void Sample::Render()

in UWPSamples/System/SystemInfoUWP/SystemInfo.cpp [144:1554]


void Sample::Render()
{
    // Don't try to render anything before the first Update.
    if (m_timer.GetFrameCount() == 0)
    {
        return;
    }

    Clear();

    auto context = m_deviceResources->GetD3DDeviceContext();
    PIXBeginEvent(context, PIX_COLOR_DEFAULT, L"Render");
    
    auto fullscreen = m_deviceResources->GetOutputSize();

    auto safeRect = Viewport::ComputeTitleSafeArea(fullscreen.right - fullscreen.left, fullscreen.bottom - fullscreen.top);

    float mid = float(safeRect.left) + float(safeRect.right - safeRect.left) / 2.f;

    m_batch->Begin();
    m_batch->Draw(m_background.Get(), fullscreen);

    float y = float(safeRect.top);

    XMFLOAT2 pos(float(safeRect.left), float(safeRect.bottom) - m_smallFont->GetLineSpacing());
    if (m_gamepadPresent)
    {
        DX::DrawControllerString(m_batch.get(), m_smallFont.get(), m_ctrlFont.get(), L"Use [A], [B], or [DPad] to cycle pages", pos, ATG::Colors::LightGrey, m_scale);
    }
    else
    {
        m_smallFont->DrawString(m_batch.get(), L"Use Left/Right to cycle pages", pos, ATG::Colors::LightGrey, 0, Vector2::Zero, m_scale);
    }

    float spacer = XMVectorGetX(m_smallFont->MeasureString(L"X")*m_scale);

    float left = mid - spacer;
    float right = mid + spacer;

    switch (static_cast<InfoPage>(m_current))
    {
    case InfoPage::SYSTEMINFO:
        {
            y += DrawStringCenter(m_batch.get(), m_largeFont.get(), L"GetNativeSystemInfo", mid, y, ATG::Colors::LightGrey, m_scale);

            SYSTEM_INFO info = {};
            GetNativeSystemInfo(&info);

            const wchar_t* arch = L"UNKNOWN";
            switch (info.wProcessorArchitecture)
            {
            case PROCESSOR_ARCHITECTURE_AMD64:  arch = L"AMD64"; break;
            case PROCESSOR_ARCHITECTURE_ARM:    arch = L"ARM"; break;
            case PROCESSOR_ARCHITECTURE_ARM64:  arch = L"ARM64"; break;
            case PROCESSOR_ARCHITECTURE_INTEL:  arch = L"INTEL"; break;
            }

            DrawStringLeft(m_batch.get(), m_smallFont.get(), L"wProcessorArchitecture", left, y, m_scale);
            y += DrawStringRight(m_batch.get(), m_smallFont.get(), arch, right, y, m_scale);

            wchar_t buff[128] = {};
            swprintf_s(buff, L"%u", info.wProcessorLevel);
            DrawStringLeft(m_batch.get(), m_smallFont.get(), L"wProcessorLevel", left, y, m_scale);
            y += DrawStringRight(m_batch.get(), m_smallFont.get(), buff, right, y, m_scale);

            swprintf_s(buff, L"%04X", info.wProcessorRevision);
            DrawStringLeft(m_batch.get(), m_smallFont.get(), L"wProcessorRevision", left, y, m_scale);
            y += DrawStringRight(m_batch.get(), m_smallFont.get(), buff, right, y, m_scale);

            swprintf_s(buff, L"%zX", info.dwActiveProcessorMask);
            DrawStringLeft(m_batch.get(), m_smallFont.get(), L"dwActiveProcessorMask", left, y, m_scale);
            y += DrawStringRight(m_batch.get(), m_smallFont.get(), buff, right, y, m_scale);

            swprintf_s(buff, L"%u", info.dwNumberOfProcessors);
            DrawStringLeft(m_batch.get(), m_smallFont.get(), L"dwNumberOfProcessors", left, y, m_scale);
            y += DrawStringRight(m_batch.get(), m_smallFont.get(), buff, right, y, m_scale);

            swprintf_s(buff, L"%u", info.dwPageSize);
            DrawStringLeft(m_batch.get(), m_smallFont.get(), L"dwPageSize", left, y, m_scale);
            y += DrawStringRight(m_batch.get(), m_smallFont.get(), buff, right, y, m_scale);

            swprintf_s(buff, L"%u", info.dwAllocationGranularity);
            DrawStringLeft(m_batch.get(), m_smallFont.get(), L"dwAllocationGranularity", left, y, m_scale);
            y += DrawStringRight(m_batch.get(), m_smallFont.get(), buff, right, y, m_scale);

            swprintf_s(buff, L"%p", info.lpMinimumApplicationAddress);
            DrawStringLeft(m_batch.get(), m_smallFont.get(), L"lpMinimumApplicationAddress", left, y, m_scale);
            y += DrawStringRight(m_batch.get(), m_smallFont.get(), buff, right, y, m_scale);

            swprintf_s(buff, L"%p", info.lpMaximumApplicationAddress);
            DrawStringLeft(m_batch.get(), m_smallFont.get(), L"lpMaximumApplicationAddress", left, y, m_scale);
            y += DrawStringRight(m_batch.get(), m_smallFont.get(), buff, right, y, m_scale);
        }
        break;

    case InfoPage::GETPROCESSINFO:
        {
            y += DrawStringCenter(m_batch.get(), m_largeFont.get(), L"GetProcessInformation", mid, y, ATG::Colors::LightGrey, m_scale);

            APP_MEMORY_INFORMATION info = {};
            if (GetProcessInformation(GetCurrentProcess(), ProcessAppMemoryInfo, &info, sizeof(info)))
            {
                auto ac = static_cast<uint32_t>(info.AvailableCommit / (1024 * 1024));
                auto pc = static_cast<uint32_t>(info.PrivateCommitUsage / (1024 * 1024));
                auto ppc = static_cast<uint32_t>(info.PeakPrivateCommitUsage / (1024 * 1024));
                auto tc = static_cast<uint32_t>(info.TotalCommitUsage / (1024 * 1024));

                wchar_t buff[128] = {};
                swprintf_s(buff, L"%u (MiB)", ac);
                DrawStringLeft(m_batch.get(), m_smallFont.get(), L"AvailableCommit", left, y, m_scale);
                y += DrawStringRight(m_batch.get(), m_smallFont.get(), buff, right, y, m_scale);

                swprintf_s(buff, L"%u (MiB)", pc);
                DrawStringLeft(m_batch.get(), m_smallFont.get(), L"PrivateCommitUsage", left, y, m_scale);
                y += DrawStringRight(m_batch.get(), m_smallFont.get(), buff, right, y, m_scale);

                swprintf_s(buff, L"%u (MiB)", ppc);
                DrawStringLeft(m_batch.get(), m_smallFont.get(), L"PeakPrivateCommitUsage", left, y, m_scale);
                y += DrawStringRight(m_batch.get(), m_smallFont.get(), buff, right, y, m_scale);

                swprintf_s(buff, L"%u (MiB)", tc);
                DrawStringLeft(m_batch.get(), m_smallFont.get(), L"TotalCommitUsage", left, y, m_scale);
                y += DrawStringRight(m_batch.get(), m_smallFont.get(), buff, right, y, m_scale);
            }
        }
        break;

    case InfoPage::GLOBALMEMORYSTATUS:
        {
            y += DrawStringCenter(m_batch.get(), m_largeFont.get(), L"GlobalMemoryStatusEx", mid, y, ATG::Colors::LightGrey, m_scale);

            MEMORYSTATUSEX info = {};
            info.dwLength = sizeof(MEMORYSTATUSEX);
            if (GlobalMemoryStatusEx(&info))
            {
                auto tphys = static_cast<uint32_t>(info.ullTotalPhys / (1024 * 1024));
                auto aphys = static_cast<uint32_t>(info.ullAvailPhys / (1024 * 1024));
                auto tpage = static_cast<uint32_t>(info.ullTotalPageFile / (1024 * 1024));
                auto apage = static_cast<uint32_t>(info.ullAvailPageFile / (1024 * 1024));
                auto tvirt = static_cast<uint32_t>(info.ullTotalVirtual / (1024 * 1024));
                auto avirt = static_cast<uint32_t>(info.ullAvailVirtual / (1024 * 1024));

                wchar_t buff[128] = {};
                swprintf_s(buff, L"%u / %u (MB)", aphys, tphys);
                DrawStringLeft(m_batch.get(), m_smallFont.get(), L"Physical Memory", left, y, m_scale);
                y += DrawStringRight(m_batch.get(), m_smallFont.get(), buff, right, y, m_scale);

                swprintf_s(buff, L"%u / %u (MB)", apage, tpage);
                DrawStringLeft(m_batch.get(), m_smallFont.get(), L"Page File", left, y, m_scale);
                y += DrawStringRight(m_batch.get(), m_smallFont.get(), buff, right, y, m_scale);

                swprintf_s(buff, L"%u (MB)", tvirt);
                DrawStringLeft(m_batch.get(), m_smallFont.get(), L"Total Virtual Memory", left, y, m_scale);
                y += DrawStringRight(m_batch.get(), m_smallFont.get(), buff, right, y, m_scale);

                swprintf_s(buff, L"%u (MB)", avirt);
                DrawStringLeft(m_batch.get(), m_smallFont.get(), L"Available VM", left, y, m_scale);
                y += DrawStringRight(m_batch.get(), m_smallFont.get(), buff, right, y, m_scale);

                if (info.ullAvailExtendedVirtual > 0)
                {
                    auto axvirt = static_cast<uint32_t>(info.ullAvailExtendedVirtual / (1024 * 1024));

                    swprintf_s(buff, L"%u (MB)", axvirt);
                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"Available Extended VM", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), buff, right, y, m_scale);
                }
            }
        }
        break;

    case InfoPage::ANALYTICSINFO:
        {
            y += DrawStringCenter(m_batch.get(), m_largeFont.get(), L"AnalyticsInfo", mid, y, ATG::Colors::LightGrey, m_scale);

            auto deviceForm = Windows::System::Profile::AnalyticsInfo::DeviceForm;

            DrawStringLeft(m_batch.get(), m_smallFont.get(), L"DeviceForm", left, y, m_scale);
            y += DrawStringRight(m_batch.get(), m_smallFont.get(), deviceForm->Data(), right, y, m_scale);

            auto versionInfo = Windows::System::Profile::AnalyticsInfo::VersionInfo;

            DrawStringLeft(m_batch.get(), m_smallFont.get(), L"DeviceFamily", left, y, m_scale);
            y += DrawStringRight(m_batch.get(), m_smallFont.get(), versionInfo->DeviceFamily->Data(), right, y, m_scale);

            DrawStringLeft(m_batch.get(), m_smallFont.get(), L"DeviceFamilyVersion", left, y, m_scale);
            y += DrawStringRight(m_batch.get(), m_smallFont.get(), versionInfo->DeviceFamilyVersion->Data(), right, y, m_scale);

            // For real-world use just log it as an opaque string, and do the decode in the reader instead
            LARGE_INTEGER li;
            li.QuadPart = _wtoi64(versionInfo->DeviceFamilyVersion->Data());

            wchar_t buff[128] = {};
            swprintf_s(buff, L"%u.%u.%u.%u", HIWORD(li.HighPart), LOWORD(li.HighPart), HIWORD(li.LowPart), LOWORD(li.LowPart));
            y += DrawStringRight(m_batch.get(), m_smallFont.get(), buff, right, y, m_scale);
        }
        break;

    case InfoPage::EASCLIENTINFO:
        {
            y += DrawStringCenter(m_batch.get(), m_largeFont.get(), L"EasClientDeviceInformation", mid, y, ATG::Colors::LightGrey, m_scale);

            using namespace Windows::Security::ExchangeActiveSyncProvisioning;

            auto easinfo = ref new EasClientDeviceInformation;

            DrawStringLeft(m_batch.get(), m_smallFont.get(), L"Id", left, y, m_scale);
            y += DrawStringRight(m_batch.get(), m_smallFont.get(), easinfo->Id.ToString()->Data(), right, y, m_scale);

            DrawStringLeft(m_batch.get(), m_smallFont.get(), L"FriendlyName", left, y, m_scale);
            y += DrawStringRight(m_batch.get(), m_smallFont.get(), easinfo->FriendlyName->Data(), right, y, m_scale);

            DrawStringLeft(m_batch.get(), m_smallFont.get(), L"OperatingSystem", left, y, m_scale);
            y += DrawStringRight(m_batch.get(), m_smallFont.get(), easinfo->OperatingSystem->Data(), right, y, m_scale);

            DrawStringLeft(m_batch.get(), m_smallFont.get(), L"SystemManufacturer", left, y, m_scale);
            y += DrawStringRight(m_batch.get(), m_smallFont.get(), easinfo->SystemManufacturer->Data(), right, y, m_scale);

            DrawStringLeft(m_batch.get(), m_smallFont.get(), L"SystemProductName", left, y, m_scale);
            y += DrawStringRight(m_batch.get(), m_smallFont.get(), easinfo->SystemProductName->Data(), right, y, m_scale);

            DrawStringLeft(m_batch.get(), m_smallFont.get(), L"SystemSku", left, y, m_scale);
            y += DrawStringRight(m_batch.get(), m_smallFont.get(), easinfo->SystemSku->Data(), right, y, m_scale);

            if (!easinfo->SystemHardwareVersion->IsEmpty())
            {
                DrawStringLeft(m_batch.get(), m_smallFont.get(), L"SystemHardwareVersion", left, y, m_scale);
                y += DrawStringRight(m_batch.get(), m_smallFont.get(), easinfo->SystemHardwareVersion->Data(), right, y, m_scale);
            }

            if (!easinfo->SystemFirmwareVersion->IsEmpty())
            {
                DrawStringLeft(m_batch.get(), m_smallFont.get(), L"SystemFirmwareVersion", left, y, m_scale);
                y += DrawStringRight(m_batch.get(), m_smallFont.get(), easinfo->SystemFirmwareVersion->Data(), right, y, m_scale);
            }
        }
        break;

    case InfoPage::GAMINGDEVICEINFO:
        {
            y += DrawStringCenter(m_batch.get(), m_largeFont.get(), L"GetGamingDeviceModelInformation", mid, y, ATG::Colors::LightGrey, m_scale);

            #if defined(NTDDI_WIN10_RS3) && (NTDDI_VERSION >= NTDDI_WIN10_RS3)

            // Requires the linker settings to include /DELAYLOAD:api-ms-win-gaming-deviceinformation-l1-1-0.dll
            //
            // Note: You can avoid the need for delay loading if you require 10.0.16299 as your minimum OS version
            //       and/or you restrict your package to the Xbox device family

            if (QueryOptionalDelayLoadedAPI(reinterpret_cast<HMODULE>(&__ImageBase),
                "api-ms-win-gaming-deviceinformation-l1-1-0.dll",
                "GetGamingDeviceModelInformation",
                0))
            {
                GAMING_DEVICE_MODEL_INFORMATION info = {};
                GetGamingDeviceModelInformation(&info);

                wchar_t buff[128] = {};
                swprintf_s(buff, L"%08X", static_cast<unsigned int>(info.vendorId));

                switch (info.vendorId)
                {
                case GAMING_DEVICE_VENDOR_ID_MICROSOFT: wcscat_s(buff, L" (Microsoft)"); break;
                }

                DrawStringLeft(m_batch.get(), m_smallFont.get(), L"VendorId", left, y, m_scale);
                y += DrawStringRight(m_batch.get(), m_smallFont.get(), buff, right, y, m_scale);

                swprintf_s(buff, L"%08X", static_cast<unsigned int>(info.deviceId));

                if (info.vendorId == GAMING_DEVICE_VENDOR_ID_MICROSOFT)
                {
                    switch (info.deviceId)
                    {
                    case GAMING_DEVICE_DEVICE_ID_XBOX_ONE: wcscat_s(buff, L" (Xbox One)"); break;
                    case GAMING_DEVICE_DEVICE_ID_XBOX_ONE_S: wcscat_s(buff, L" (Xbox One S)"); break;
                    case GAMING_DEVICE_DEVICE_ID_XBOX_ONE_X: wcscat_s(buff, L" (Xbox One X)"); break;
                    case GAMING_DEVICE_DEVICE_ID_XBOX_ONE_X_DEVKIT: wcscat_s(buff, L" (Xbox One X Dev Kit)"); break;
                    }
                }

                DrawStringLeft(m_batch.get(), m_smallFont.get(), L"DeviceId", left, y, m_scale);
                y += DrawStringRight(m_batch.get(), m_smallFont.get(), buff, right, y, m_scale);
            }
            else
            #endif
            {
                y += DrawStringCenter(m_batch.get(), m_smallFont.get(), L"This API requires Windows 10 (16299) or later", mid, y, ATG::Colors::Orange, m_scale);
            }
        }
        break;

    case InfoPage::APICONTRACT_PAGE:
        {
            y += DrawStringCenter(m_batch.get(), m_largeFont.get(), L"IsApiContractPresent", mid, y, ATG::Colors::LightGrey, m_scale);

            using namespace Windows::Foundation::Metadata;

            // https://docs.microsoft.com/en-us/uwp/extension-sdks/windows-universal-sdk

            bool isfoundation2 = ApiInformation::IsApiContractPresent("Windows.Foundation.FoundationContract", 2, 0);
            bool isfoundation3 = ApiInformation::IsApiContractPresent("Windows.Foundation.FoundationContract", 3, 0);
            bool isuniversal2 = ApiInformation::IsApiContractPresent("Windows.Foundation.UniversalApiContract", 2, 0);
            bool isuniversal3 = ApiInformation::IsApiContractPresent("Windows.Foundation.UniversalApiContract", 3, 0);
            bool isuniversal4 = ApiInformation::IsApiContractPresent("Windows.Foundation.UniversalApiContract", 4, 0);
            bool isuniversal5 = ApiInformation::IsApiContractPresent("Windows.Foundation.UniversalApiContract", 5, 0);
            bool isuniversal6 = ApiInformation::IsApiContractPresent("Windows.Foundation.UniversalApiContract", 6, 0);
            bool isuniversal7 = ApiInformation::IsApiContractPresent("Windows.Foundation.UniversalApiContract", 7, 0);
            bool isuniversal8 = ApiInformation::IsApiContractPresent("Windows.Foundation.UniversalApiContract", 8, 0);
            bool isphone = ApiInformation::IsApiContractPresent("Windows.Phone.PhoneContract", 1, 0);
            bool isstore2 = ApiInformation::IsApiContractPresent("Windows.Services.Store.StoreContract", 2, 0);
            bool isstore3 = ApiInformation::IsApiContractPresent("Windows.Services.Store.StoreContract", 3, 0);
            bool isstore4 = ApiInformation::IsApiContractPresent("Windows.Services.Store.StoreContract", 4, 0);
            bool xliveStorage = ApiInformation::IsApiContractPresent("Windows.Gaming.XboxLive.StorageApiContract", 1, 0);
            bool xliveSecure = ApiInformation::IsApiContractPresent("Windows.Networking.XboxLive.XboxLiveSecureSocketsContract", 1, 0);

            assert(ApiInformation::IsApiContractPresent("Windows.Foundation.FoundationContract", 1, 0));
            wchar_t contracts[256] = L"1.0";
            if (isfoundation2) { wcscat_s(contracts, L", 2.0"); }
            if (isfoundation3) { wcscat_s(contracts, L", 3.0"); }

            DrawStringLeft(m_batch.get(), m_smallFont.get(), L"FoundationContract", left, y, m_scale);
            y += DrawStringRight(m_batch.get(), m_smallFont.get(), contracts, right, y, m_scale);

            assert(ApiInformation::IsApiContractPresent("Windows.Foundation.UniversalApiContract", 1, 0));
            wcscpy_s(contracts, L"1.0");
            if (isuniversal2) { wcscat_s(contracts, L", 2.0"); }
            if (isuniversal3) { wcscat_s(contracts, L", 3.0"); }
            if (isuniversal4) { wcscat_s(contracts, L", 4.0"); }
            if (isuniversal5) { wcscat_s(contracts, L", 5.0"); }
            if (isuniversal6) { wcscat_s(contracts, L", 6.0"); }
            if (isuniversal7) { wcscat_s(contracts, L", 7.0"); }
            if (isuniversal8) { wcscat_s(contracts, L", 8.0"); }

            DrawStringLeft(m_batch.get(), m_smallFont.get(), L"UniversalApiContract", left, y, m_scale);
            y += DrawStringRight(m_batch.get(), m_smallFont.get(), contracts, right, y, m_scale);

            DrawStringLeft(m_batch.get(), m_smallFont.get(), L"PhoneContract", left, y, m_scale);
            y += DrawStringRight(m_batch.get(), m_smallFont.get(), isphone ? L"1.0" : L"", right, y, m_scale);

            assert(ApiInformation::IsApiContractPresent("Windows.Services.Store.StoreContract", 1, 0));
            wcscpy_s(contracts, L"1.0");
            if (isstore2) { wcscat_s(contracts, L", 2.0"); }
            if (isstore3) { wcscat_s(contracts, L", 3.0"); }
            if (isstore4) { wcscat_s(contracts, L", 4.0"); }

            DrawStringLeft(m_batch.get(), m_smallFont.get(), L"StoreContract", left, y, m_scale);
            y += DrawStringRight(m_batch.get(), m_smallFont.get(), contracts, right, y, m_scale);

            DrawStringLeft(m_batch.get(), m_smallFont.get(), L"XboxLive StorageApiContract", left, y, m_scale);
            y += DrawStringRight(m_batch.get(), m_smallFont.get(), xliveStorage ? L"1.0" : L"", right, y, m_scale);

            DrawStringLeft(m_batch.get(), m_smallFont.get(), L"XboxLive SecureSocketsContract", left, y, m_scale);
            y += DrawStringRight(m_batch.get(), m_smallFont.get(), xliveSecure ? L"1.0" : L"", right, y, m_scale);
        }
        break;

    case InfoPage::CPUSETS:
        {
            y += DrawStringCenter(m_batch.get(), m_largeFont.get(), L"GetSystemCpuSetInformation", mid, y, ATG::Colors::LightGrey, m_scale);

            ULONG retsize = 0;
            (void)GetSystemCpuSetInformation(nullptr, 0, &retsize, GetCurrentProcess(), 0);

            std::unique_ptr<uint8_t[]> data(new uint8_t[retsize]);
            if (GetSystemCpuSetInformation(
                reinterpret_cast<PSYSTEM_CPU_SET_INFORMATION>(data.get()),
                retsize, &retsize, GetCurrentProcess(), 0))
            {
                size_t logicalProcessors = 0;
                size_t parkedProcessors = 0;
                size_t allocatedProcessors = 0;
                size_t allocatedElsewhere = 0;
                size_t availableProcessors = 0;
                std::set<DWORD> cores;
                bool moreThanOneGroup = false;

                uint8_t const * ptr = data.get();
                for (DWORD size = 0; size < retsize; )
                {
                    auto info = reinterpret_cast<const SYSTEM_CPU_SET_INFORMATION*>(ptr);
                    if (info->Type == CpuSetInformation)
                    {
                        if (info->CpuSet.Group > 0)
                        {
                            moreThanOneGroup = true;
                        }
                        else
                        {
                            ++logicalProcessors;

                            if (info->CpuSet.Parked)
                            {
                                ++parkedProcessors;
                            }
                            else
                            {
                                if (info->CpuSet.Allocated)
                                {
                                    if (info->CpuSet.AllocatedToTargetProcess)
                                    {
                                        ++allocatedProcessors;
                                        ++availableProcessors;
                                        cores.insert(info->CpuSet.CoreIndex);
                                    }
                                    else
                                    {
                                        ++allocatedElsewhere;
                                    }
                                }
                                else
                                {
                                    ++availableProcessors;
                                    cores.insert(info->CpuSet.CoreIndex);
                                }
                            }
                        }
                    }
                    ptr += info->Size;
                    size += info->Size;
                }

                wchar_t buff[128] = {};
                swprintf_s(buff, L"%zu", logicalProcessors);
                DrawStringLeft(m_batch.get(), m_smallFont.get(), L"Total logical processors", left, y, m_scale);
                y += DrawStringRight(m_batch.get(), m_smallFont.get(), buff, right, y, m_scale);

                if (parkedProcessors > 0)
                {
                    swprintf_s(buff, L"%zu", parkedProcessors);
                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"Parked processors", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), buff, right, y, m_scale);
                }

                if (allocatedElsewhere > 0)
                {
                    swprintf_s(buff, L"%zu", allocatedElsewhere);
                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"Allocated to other processes", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), buff, right, y, m_scale);
                }

                swprintf_s(buff, L"%zu", availableProcessors);
                DrawStringLeft(m_batch.get(), m_smallFont.get(), L"Available logical processors", left, y, m_scale);
                y += DrawStringRight(m_batch.get(), m_smallFont.get(), buff, right, y, m_scale);

                if (allocatedProcessors > 0)
                {
                    swprintf_s(buff, L"%zu", allocatedProcessors);
                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"Allocated logical processors", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), buff, right, y, m_scale);
                }

                swprintf_s(buff, L"%zu", cores.size());
                DrawStringLeft(m_batch.get(), m_smallFont.get(), L"Available physical cores", left, y, m_scale);
                y += DrawStringRight(m_batch.get(), m_smallFont.get(), buff, right, y, m_scale);

                if (moreThanOneGroup)
                {
                    y += m_smallFont->GetLineSpacing() * m_scale;
                    y += DrawStringCenter(m_batch.get(), m_smallFont.get(), L"Note more than one group found; ignored extra groups!", mid, y, ATG::Colors::Orange, m_scale);
                }
            }
        }
        break;

    case InfoPage::DISPLAYINFO:
        {
            using namespace Windows::Graphics::Display;

            y += DrawStringCenter(m_batch.get(), m_largeFont.get(), L"DisplayInformation", mid, y, ATG::Colors::LightGrey, m_scale);

            auto displayInformation = DisplayInformation::GetForCurrentView();

            wchar_t buff[128] = {};
            swprintf_s(buff, L"%u %%", displayInformation->ResolutionScale);
            DrawStringLeft(m_batch.get(), m_smallFont.get(), L"Resolution Scale", left, y, m_scale);
            y += DrawStringRight(m_batch.get(), m_smallFont.get(), buff, right, y, m_scale);

            swprintf_s(buff, L"%.f (X:%.f  Y:%.f)", displayInformation->LogicalDpi, displayInformation->RawDpiX, displayInformation->RawDpiY);
            DrawStringLeft(m_batch.get(), m_smallFont.get(), L"Dots Per Inch (DPI)", left, y, m_scale);
            y += DrawStringRight(m_batch.get(), m_smallFont.get(), buff, right, y, m_scale);

            try
            {
                swprintf_s(buff, L"%u x %u (pixels)", displayInformation->ScreenWidthInRawPixels, displayInformation->ScreenHeightInRawPixels);
                DrawStringLeft(m_batch.get(), m_smallFont.get(), L"Screen Size", left, y, m_scale);
                y += DrawStringRight(m_batch.get(), m_smallFont.get(), buff, right, y, m_scale);
            }
            catch (...)
            {
                y += DrawStringCenter(m_batch.get(), m_smallFont.get(), L"Screen size requires Windows 10 (14393) or later", mid, y, ATG::Colors::Orange, m_scale);
            }

            const wchar_t* orientation = nullptr;
            switch (displayInformation->CurrentOrientation)
            {
            case DisplayOrientations::Landscape: orientation = L"Landscape"; break;
            case DisplayOrientations::LandscapeFlipped: orientation = L"Landscape (flipped)"; break;
            case DisplayOrientations::Portrait: orientation = L"Portrait"; break;
            case DisplayOrientations::PortraitFlipped: orientation = L"Portrait (flipped)"; break;
            default: orientation = L"None"; break;
            }
            DrawStringLeft(m_batch.get(), m_smallFont.get(), L"Current Orientation", left, y, m_scale);
            y += DrawStringRight(m_batch.get(), m_smallFont.get(), orientation, right, y, m_scale);

            switch (displayInformation->NativeOrientation)
            {
            case DisplayOrientations::Landscape: orientation = L"Landscape"; break;
            case DisplayOrientations::LandscapeFlipped: orientation = L"Landscape (flipped)"; break;
            case DisplayOrientations::Portrait: orientation = L"Portrait"; break;
            case DisplayOrientations::PortraitFlipped: orientation = L"Portrait (flipped)"; break;
            default: orientation = L"None"; break;
            }
            DrawStringLeft(m_batch.get(), m_smallFont.get(), L"Native Orientation", left, y, m_scale);
            y += DrawStringRight(m_batch.get(), m_smallFont.get(), orientation, right, y, m_scale);

            DrawStringLeft(m_batch.get(), m_smallFont.get(), L"Stereoscopic 3D", left, y, m_scale);
            y += DrawStringRight(m_batch.get(), m_smallFont.get(), displayInformation->StereoEnabled ? L"Enabled" : L"Disabled", right, y, m_scale);
        }
        break;

    case InfoPage::DXGI:
        {
            y += DrawStringCenter(m_batch.get(), m_largeFont.get(), L"DXGI", mid, y, ATG::Colors::LightGrey, m_scale);

            y += DrawStringCenter(m_batch.get(), m_smallFont.get(), L"DXGI_OUTPUT_DESC", mid, y, ATG::Colors::OffWhite, m_scale);

            ComPtr<IDXGIOutput> output;
            HRESULT hr = m_deviceResources->GetSwapChain()->GetContainingOutput(output.GetAddressOf());
            if (SUCCEEDED(hr))
            {
                DXGI_OUTPUT_DESC outputDesc = {};
                DX::ThrowIfFailed(output->GetDesc(&outputDesc));

                DrawStringLeft(m_batch.get(), m_smallFont.get(), L"DeviceName", left, y, m_scale);
                y += DrawStringRight(m_batch.get(), m_smallFont.get(), outputDesc.DeviceName, right, y, m_scale);

                wchar_t buff[128] = {};
                swprintf_s(buff, L"%u,%u,%u,%u", outputDesc.DesktopCoordinates.left, outputDesc.DesktopCoordinates.top, outputDesc.DesktopCoordinates.right, outputDesc.DesktopCoordinates.bottom );
                DrawStringLeft(m_batch.get(), m_smallFont.get(), L"DesktopCoordinates", left, y, m_scale);
                y += DrawStringRight(m_batch.get(), m_smallFont.get(), buff, right, y, m_scale);

                const wchar_t* rotation = L"UNSPECIFIED";
                switch (outputDesc.Rotation)
                {
                case DXGI_MODE_ROTATION_IDENTITY: rotation = L"IDENTITY"; break;
                case DXGI_MODE_ROTATION_ROTATE90: rotation = L"ROTATE90"; break;
                case DXGI_MODE_ROTATION_ROTATE180: rotation = L"ROTATE180"; break;
                case DXGI_MODE_ROTATION_ROTATE270: rotation = L"ROTATION270"; break;
                }

                DrawStringLeft(m_batch.get(), m_smallFont.get(), L"Rotation", left, y, m_scale);
                y += DrawStringRight(m_batch.get(), m_smallFont.get(), rotation, right, y, m_scale) * 1.25f;


                #if defined(NTDDI_WIN10_RS2) && (NTDDI_VERSION >= NTDDI_WIN10_RS2)
                ComPtr<IDXGIOutput6> output6;
                if (SUCCEEDED(output.As(&output6)))
                {
                    DXGI_OUTPUT_DESC1 outputDesc6;
                    DX::ThrowIfFailed(output6->GetDesc1(&outputDesc6));

                    const wchar_t* colorSpace = L"sRGB";

                    switch (outputDesc6.ColorSpace)
                    {
                    case DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020: colorSpace = L"HDR10"; break;
                    case DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709: colorSpace = L"Linear"; break;
                    }

                    y += DrawStringCenter(m_batch.get(), m_smallFont.get(), L"DXGI_OUTPUT_DESC1", mid, y, ATG::Colors::OffWhite, m_scale);

                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"ColorSpace", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), colorSpace, right, y, m_scale);
                }
                #endif

                y += DrawStringCenter(m_batch.get(), m_smallFont.get(), L"DXGI_ADAPTER_DESC", mid, y, ATG::Colors::OffWhite, m_scale);

                ComPtr<IDXGIAdapter> adapter;
                if (SUCCEEDED(output->GetParent(IID_PPV_ARGS(adapter.GetAddressOf()))))
                {
                    DXGI_ADAPTER_DESC adapterDesc = {};
                    DX::ThrowIfFailed(adapter->GetDesc(&adapterDesc));

                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"Description", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), adapterDesc.Description, right, y, m_scale);

                    swprintf_s(buff, L"%04X / %04X", adapterDesc.VendorId, adapterDesc.DeviceId);
                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"VendorId / DeviceId", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), buff, right, y, m_scale);

                    swprintf_s(buff, L"%08X / %u", adapterDesc.SubSysId, adapterDesc.Revision);
                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"SubSysId / Revision", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), buff, right, y, m_scale);

                    auto dvm = static_cast<uint32_t>(adapterDesc.DedicatedVideoMemory / (1024 * 1024));
                    auto dsm = static_cast<uint32_t>(adapterDesc.DedicatedSystemMemory / (1024 * 1024));
                    auto ssm = static_cast<uint32_t>(adapterDesc.SharedSystemMemory / (1024 * 1024));

                    swprintf_s(buff, L"%u (MiB)", dvm);
                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"DedicatedVideoMemory", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), buff, right, y, m_scale);

                    swprintf_s(buff, L"%u (MiB) / %u (MiB)", dsm, ssm);
                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"Dedicated / Shared SystemMemory", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), buff, right, y, m_scale);
                }
            }
            else
            {
                wchar_t buff[128] = {};
                swprintf_s(buff, L"GetContainingOutput failed with %08X", static_cast<unsigned int>(hr));
                y += DrawStringCenter(m_batch.get(), m_smallFont.get(), buff, mid, y, ATG::Colors::Orange, m_scale);
            }

            y += m_smallFont->GetLineSpacing() * m_scale;

            ComPtr<IDXGIDevice3> dxgiDevice;
            if (SUCCEEDED(m_deviceResources->GetD3DDevice()->QueryInterface(IID_PPV_ARGS(dxgiDevice.GetAddressOf()))))
            {
                ComPtr<IDXGIAdapter> dxgiAdapter;
                if (SUCCEEDED(dxgiDevice->GetAdapter(dxgiAdapter.GetAddressOf())))
                {
                    ComPtr<IDXGIFactory5> dxgiFactory;
                    if (SUCCEEDED(dxgiAdapter->GetParent(IID_PPV_ARGS(dxgiFactory.GetAddressOf()))))
                    {
                        BOOL allowTearing = FALSE;
                        if (SUCCEEDED(dxgiFactory->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING, &allowTearing, sizeof(BOOL))))
                        {
                            const wchar_t* dxgiver = L"DXGI 1.5";
                            #if defined(NTDDI_WIN10_RS4) && (NTDDI_VERSION >= NTDDI_WIN10_RS4)
                            {
                                ComPtr<IDXGIFactory6> dxgiFactory6;
                                if (SUCCEEDED(dxgiFactory.As(&dxgiFactory6)))
                                {
                                    dxgiver = L"DXGI 1.6";
                                }
                            }
                            #endif

                            y += DrawStringCenter(m_batch.get(), m_smallFont.get(), dxgiver, mid, y, ATG::Colors::OffWhite, m_scale);

                            DrawStringLeft(m_batch.get(), m_smallFont.get(), L"Allow Tearing", left, y, m_scale);
                            y += DrawStringRight(m_batch.get(), m_smallFont.get(), allowTearing ? L"true" : L"false", right, y, m_scale);
                        }
                    }
                }
            }
        }
        break;

    case InfoPage::DIRECT3D11_1:
        {
            y += DrawStringCenter(m_batch.get(), m_largeFont.get(), L"Direct3D 11.1", mid, y, ATG::Colors::LightGrey, m_scale);

            const wchar_t* featLevel = L"Unknown";
            switch (m_deviceResources->GetDeviceFeatureLevel())
            {
            case D3D_FEATURE_LEVEL_9_1: featLevel = L"9.1"; break;
            case D3D_FEATURE_LEVEL_9_2: featLevel = L"9.2"; break;
            case D3D_FEATURE_LEVEL_9_3: featLevel = L"9.3"; break;
            case D3D_FEATURE_LEVEL_10_0: featLevel = L"10.0"; break;
            case D3D_FEATURE_LEVEL_10_1: featLevel = L"10.1"; break;
            case D3D_FEATURE_LEVEL_11_0: featLevel = L"11.0"; break;
            case D3D_FEATURE_LEVEL_11_1: featLevel = L"11.1"; break;
            case D3D_FEATURE_LEVEL_12_0: featLevel = L"12.0"; break;
            case D3D_FEATURE_LEVEL_12_1: featLevel = L"12.1"; break;
            }

            DrawStringLeft(m_batch.get(), m_smallFont.get(), L"Hardware Feature Level", left, y, m_scale);
            y += DrawStringRight(m_batch.get(), m_smallFont.get(), featLevel, right, y, m_scale);

            auto device = m_deviceResources->GetD3DDevice();

            D3D11_FEATURE_DATA_DOUBLES doubles = {};
            if (SUCCEEDED(device->CheckFeatureSupport(D3D11_FEATURE_DOUBLES, &doubles, sizeof(doubles))))
            {
                DrawStringLeft(m_batch.get(), m_smallFont.get(), L"DoublePrecisionFloatShaderOps", left, y, m_scale);
                y += DrawStringRight(m_batch.get(), m_smallFont.get(), doubles.DoublePrecisionFloatShaderOps ? L"true" : L"false", right, y, m_scale);
            }

            D3D11_FEATURE_DATA_D3D10_X_HARDWARE_OPTIONS d3d10compute = {};
            if (FAILED(device->CheckFeatureSupport(D3D11_FEATURE_D3D10_X_HARDWARE_OPTIONS, &d3d10compute, sizeof(d3d10compute))))
            {
                memset(&d3d10compute, 0, sizeof(d3d10compute));
            }

            const wchar_t* directcompute = L"No";
            if (m_deviceResources->GetDeviceFeatureLevel() >= D3D_FEATURE_LEVEL_11_0)
            {
                directcompute = L"5.0";
            }
            else if (d3d10compute.ComputeShaders_Plus_RawAndStructuredBuffers_Via_Shader_4_x)
            {
                directcompute = (m_deviceResources->GetDeviceFeatureLevel() >= D3D_FEATURE_LEVEL_10_1) ? L"4.1" : L"4.0";
            }

            DrawStringLeft(m_batch.get(), m_smallFont.get(), L"DirectCompute", left, y, m_scale);
            y += DrawStringRight(m_batch.get(), m_smallFont.get(), directcompute, right, y, m_scale);

            D3D11_FEATURE_DATA_D3D11_OPTIONS d3d11opts = {};
            if (SUCCEEDED(device->CheckFeatureSupport(D3D11_FEATURE_D3D11_OPTIONS, &d3d11opts, sizeof(d3d11opts))))
            {
                DrawStringLeft(m_batch.get(), m_smallFont.get(), L"OutputMergerLogicOp", left, y, m_scale);
                y += DrawStringRight(m_batch.get(), m_smallFont.get(), d3d11opts.OutputMergerLogicOp ? L"true" : L"false", right, y, m_scale);

                DrawStringLeft(m_batch.get(), m_smallFont.get(), L"ClearView", left, y, m_scale);
                y += DrawStringRight(m_batch.get(), m_smallFont.get(), d3d11opts.ClearView ? L"true" : L"false", right, y, m_scale);

                DrawStringLeft(m_batch.get(), m_smallFont.get(), L"CBPartialUpdate", left, y, m_scale);
                y += DrawStringRight(m_batch.get(), m_smallFont.get(), d3d11opts.ConstantBufferPartialUpdate ? L"true" : L"false", right, y, m_scale);

                DrawStringLeft(m_batch.get(), m_smallFont.get(), L"CBOffsetting", left, y, m_scale);
                y += DrawStringRight(m_batch.get(), m_smallFont.get(), d3d11opts.ConstantBufferOffsetting ? L"true" : L"false", right, y, m_scale);

                DrawStringLeft(m_batch.get(), m_smallFont.get(), L"MapNoOverwriteOnDynamicCB", left, y, m_scale);
                y += DrawStringRight(m_batch.get(), m_smallFont.get(), d3d11opts.MapNoOverwriteOnDynamicConstantBuffer ? L"true" : L"false", right, y, m_scale);

                DrawStringLeft(m_batch.get(), m_smallFont.get(), L"MapNoOverwriteOnDynamicSRV", left, y, m_scale);
                y += DrawStringRight(m_batch.get(), m_smallFont.get(), d3d11opts.MapNoOverwriteOnDynamicBufferSRV ? L"true" : L"false", right, y, m_scale);

                DrawStringLeft(m_batch.get(), m_smallFont.get(), L"SAD4ShaderInstructions", left, y, m_scale);
                y += DrawStringRight(m_batch.get(), m_smallFont.get(), d3d11opts.SAD4ShaderInstructions ? L"true" : L"false", right, y, m_scale);

                DrawStringLeft(m_batch.get(), m_smallFont.get(), L"ExtendedDoublesShaderInstructions", left, y, m_scale);
                y += DrawStringRight(m_batch.get(), m_smallFont.get(), d3d11opts.ExtendedDoublesShaderInstructions ? L"true" : L"false", right, y, m_scale);
            }

            D3D11_FEATURE_DATA_ARCHITECTURE_INFO arch = {};
            if (SUCCEEDED(device->CheckFeatureSupport(D3D11_FEATURE_ARCHITECTURE_INFO, &arch, sizeof(arch))))
            {
                DrawStringLeft(m_batch.get(), m_smallFont.get(), L"TileBasedDeferredRenderer", left, y, m_scale);
                y += DrawStringRight(m_batch.get(), m_smallFont.get(), arch.TileBasedDeferredRenderer ? L"true" : L"false", right, y, m_scale);
            }
        }
        break;

    case InfoPage::DIRECT3D11_2:
        {
            y += DrawStringCenter(m_batch.get(), m_largeFont.get(), L"Direct3D 11.2", mid, y, ATG::Colors::LightGrey, m_scale);

            auto device = m_deviceResources->GetD3DDevice();

            D3D11_FEATURE_DATA_D3D11_OPTIONS1 d3d11opts1 = {};
            if (SUCCEEDED(device->CheckFeatureSupport(D3D11_FEATURE_D3D11_OPTIONS1, &d3d11opts1, sizeof(d3d11opts1))))
            {
                const wchar_t* tiledTier = L"Unknown";
                switch (d3d11opts1.TiledResourcesTier)
                {
                case D3D11_TILED_RESOURCES_NOT_SUPPORTED: tiledTier = L"Not supported"; break;
                case D3D11_TILED_RESOURCES_TIER_1: tiledTier = L"Tier 1"; break;
                case D3D11_TILED_RESOURCES_TIER_2: tiledTier = L"Tier 2"; break;
                case D3D11_TILED_RESOURCES_TIER_3: tiledTier = L"Tier 3"; break;
                }

                DrawStringLeft(m_batch.get(), m_smallFont.get(), L"TiledResourcesTier", left, y, m_scale);
                y += DrawStringRight(m_batch.get(), m_smallFont.get(), tiledTier, right, y, m_scale);

                DrawStringLeft(m_batch.get(), m_smallFont.get(), L"MinMaxFiltering", left, y, m_scale);
                y += DrawStringRight(m_batch.get(), m_smallFont.get(), d3d11opts1.MinMaxFiltering ? L"true" : L"false", right, y, m_scale);

                DrawStringLeft(m_batch.get(), m_smallFont.get(), L"ClearView(...)DepthOnlyFormats", left, y, m_scale);
                y += DrawStringRight(m_batch.get(), m_smallFont.get(), d3d11opts1.ClearViewAlsoSupportsDepthOnlyFormats ? L"true" : L"false", right, y, m_scale);

                DrawStringLeft(m_batch.get(), m_smallFont.get(), L"MapOnDefaultBuffers", left, y, m_scale);
                y += DrawStringRight(m_batch.get(), m_smallFont.get(), d3d11opts1.MapOnDefaultBuffers ? L"true" : L"false", right, y, m_scale);
            }
        }
        break;

    case InfoPage::DIRECT3D11_3:
        {
            y += DrawStringCenter(m_batch.get(), m_largeFont.get(), L"Direct3D 11.3", mid, y, ATG::Colors::LightGrey, m_scale);

            auto device = m_deviceResources->GetD3DDevice();

            D3D11_FEATURE_DATA_D3D11_OPTIONS2 d3d11opts2 = {};
            if (SUCCEEDED(device->CheckFeatureSupport(D3D11_FEATURE_D3D11_OPTIONS2, &d3d11opts2, sizeof(d3d11opts2))))
            {
                const wchar_t* cRastTier = L"Unknown";
                switch (d3d11opts2.ConservativeRasterizationTier)
                {
                case D3D11_CONSERVATIVE_RASTERIZATION_NOT_SUPPORTED: cRastTier = L"Not supported"; break;
                case D3D11_CONSERVATIVE_RASTERIZATION_TIER_1: cRastTier = L"Tier 1"; break;
                case D3D11_CONSERVATIVE_RASTERIZATION_TIER_2: cRastTier = L"Tier 2"; break;
                case D3D11_CONSERVATIVE_RASTERIZATION_TIER_3: cRastTier = L"Tier 3"; break;
                }

                DrawStringLeft(m_batch.get(), m_smallFont.get(), L"ConservativeRasterizationTier", left, y, m_scale);
                y += DrawStringRight(m_batch.get(), m_smallFont.get(), cRastTier, right, y, m_scale);

                DrawStringLeft(m_batch.get(), m_smallFont.get(), L"PSSpecifiedStencilRefSupported", left, y, m_scale);
                y += DrawStringRight(m_batch.get(), m_smallFont.get(), d3d11opts2.PSSpecifiedStencilRefSupported ? L"true" : L"false", right, y, m_scale);

                DrawStringLeft(m_batch.get(), m_smallFont.get(), L"TypedUAVLoadAdditionalFormats", left, y, m_scale);
                y += DrawStringRight(m_batch.get(), m_smallFont.get(), d3d11opts2.TypedUAVLoadAdditionalFormats ? L"true" : L"false", right, y, m_scale);

                DrawStringLeft(m_batch.get(), m_smallFont.get(), L"ROVsSupported", left, y, m_scale);
                y += DrawStringRight(m_batch.get(), m_smallFont.get(), d3d11opts2.ROVsSupported ? L"true" : L"false", right, y, m_scale);

                DrawStringLeft(m_batch.get(), m_smallFont.get(), L"MapOnDefaultTextures", left, y, m_scale);
                y += DrawStringRight(m_batch.get(), m_smallFont.get(), d3d11opts2.MapOnDefaultTextures ? L"true" : L"false", right, y, m_scale);

                DrawStringLeft(m_batch.get(), m_smallFont.get(), L"StandardSwizzle", left, y, m_scale);
                y += DrawStringRight(m_batch.get(), m_smallFont.get(), d3d11opts2.StandardSwizzle ? L"true" : L"false", right, y, m_scale);

                DrawStringLeft(m_batch.get(), m_smallFont.get(), L"UnifiedMemoryArchitecture", left, y, m_scale);
                y += DrawStringRight(m_batch.get(), m_smallFont.get(), d3d11opts2.UnifiedMemoryArchitecture ? L"true" : L"false", right, y, m_scale);
            }

            D3D11_FEATURE_DATA_D3D11_OPTIONS3 d3d11opts3 = {};
            if (SUCCEEDED(device->CheckFeatureSupport(D3D11_FEATURE_D3D11_OPTIONS3, &d3d11opts3, sizeof(d3d11opts3))))
            {
                DrawStringLeft(m_batch.get(), m_smallFont.get(), L"VPAndRT(...)Rasterizer", left, y, m_scale);
                y += DrawStringRight(m_batch.get(), m_smallFont.get(), d3d11opts3.VPAndRTArrayIndexFromAnyShaderFeedingRasterizer ? L"true" : L"false", right, y, m_scale);
            }
        }
        break;

    case InfoPage::DIRECT3D11_4:
        {
            y += DrawStringCenter(m_batch.get(), m_largeFont.get(), L"Direct3D 11.4", mid, y, ATG::Colors::LightGrey, m_scale);

            auto device = m_deviceResources->GetD3DDevice();

            ComPtr<ID3D11Device4> device4;
            if (SUCCEEDED(device->QueryInterface(IID_PPV_ARGS(device4.GetAddressOf()))))
            {
                // Optional Direct3D 11.4 features for Windows 10 Anniversary Update
                D3D11_FEATURE_DATA_D3D11_OPTIONS4 d3d11opts4 = {};
                if (SUCCEEDED(device->CheckFeatureSupport(D3D11_FEATURE_D3D11_OPTIONS4, &d3d11opts4, sizeof(d3d11opts4))))
                {
                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"Extended NV12 Shared", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), d3d11opts4.ExtendedNV12SharedTextureSupported ? L"true" : L"false", right, y, m_scale);
                }
                else
                {
                    y += DrawStringCenter(m_batch.get(), m_smallFont.get(), L"Partial support with Windows 10 Version 1511", mid, y, ATG::Colors::OffWhite, m_scale);
                }
            }
            else
            {
                y += DrawStringCenter(m_batch.get(), m_smallFont.get(), L"Not supported with Windows 10 RTM", mid, y, ATG::Colors::Orange, m_scale);
            }
        }
        break;

    case InfoPage::DIRECT3D12:
        {
            y += DrawStringCenter(m_batch.get(), m_largeFont.get(), L"Direct3D 12", mid, y, ATG::Colors::LightGrey, m_scale);

            auto device = m_deviceResources->GetD3DDevice12();
            
            if (!device)
            {
                y += DrawStringCenter(m_batch.get(), m_smallFont.get(), L"Not supported", mid, y, ATG::Colors::Orange, m_scale);
            }
            else
            {
                // Determine highest feature level
                static const D3D_FEATURE_LEVEL s_featureLevels[] =
                {
#if defined(NTDDI_WIN10_FE) && (NTDDI_VERSION >= NTDDI_WIN10_FE)
                    D3D_FEATURE_LEVEL_12_2,
#endif
                    D3D_FEATURE_LEVEL_12_1,
                    D3D_FEATURE_LEVEL_12_0,
                    D3D_FEATURE_LEVEL_11_1,
                    D3D_FEATURE_LEVEL_11_0,
                };

                D3D12_FEATURE_DATA_FEATURE_LEVELS featLevels =
                {
                    _countof(s_featureLevels), s_featureLevels, D3D_FEATURE_LEVEL_11_0
                };

                if (FAILED(device->CheckFeatureSupport(D3D12_FEATURE_FEATURE_LEVELS, &featLevels, sizeof(featLevels))))
                {
                    featLevels.MaxSupportedFeatureLevel = D3D_FEATURE_LEVEL_9_1;
                }

                const wchar_t* featLevel = L"Unknown";
                switch (featLevels.MaxSupportedFeatureLevel)
                {
                case D3D_FEATURE_LEVEL_11_0: featLevel = L"11.0"; break;
                case D3D_FEATURE_LEVEL_11_1: featLevel = L"11.1"; break;
                case D3D_FEATURE_LEVEL_12_0: featLevel = L"12.0"; break;
                case D3D_FEATURE_LEVEL_12_1: featLevel = L"12.1"; break;
#if defined(NTDDI_WIN10_FE) && (NTDDI_VERSION >= NTDDI_WIN10_FE)
                case D3D_FEATURE_LEVEL_12_2: featLevel = L"12.2"; break;
#endif
                }

                DrawStringLeft(m_batch.get(), m_smallFont.get(), L"Hardware Feature Level", left, y, m_scale);
                y += DrawStringRight(m_batch.get(), m_smallFont.get(), featLevel, right, y, m_scale);

                // Determine maximum shader model / root signature
                D3D12_FEATURE_DATA_ROOT_SIGNATURE rootSig = {};
                rootSig.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_1;
                if (FAILED(device->CheckFeatureSupport(D3D12_FEATURE_ROOT_SIGNATURE, &rootSig, sizeof(rootSig))))
                {
                    rootSig.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_0;
                }

                const wchar_t* rootSigVer = L"Unknown";
                switch (rootSig.HighestVersion)
                {
                case D3D_ROOT_SIGNATURE_VERSION_1_0: rootSigVer = L"1.0"; break;
                case D3D_ROOT_SIGNATURE_VERSION_1_1: rootSigVer = L"1.1"; break;
                }

                D3D12_FEATURE_DATA_SHADER_MODEL shaderModel = {};
                #if defined(NTDDI_WIN10_FE) && (NTDDI_VERSION >= NTDDI_WIN10_FE)
                shaderModel.HighestShaderModel = D3D_SHADER_MODEL_6_7;
                #elif defined(NTDDI_WIN10_VB) && (NTDDI_VERSION >= NTDDI_WIN10_VB)
                shaderModel.HighestShaderModel = D3D_SHADER_MODEL_6_6;
                #elif defined(NTDDI_WIN10_19H1) && (NTDDI_VERSION >= NTDDI_WIN10_19H1)
                shaderModel.HighestShaderModel = D3D_SHADER_MODEL_6_5;
                #elif defined(NTDDI_WIN10_RS5) && (NTDDI_VERSION >= NTDDI_WIN10_RS5)
                shaderModel.HighestShaderModel = D3D_SHADER_MODEL_6_4;
                #elif defined(NTDDI_WIN10_RS4) && (NTDDI_VERSION >= NTDDI_WIN10_RS4)
                shaderModel.HighestShaderModel = D3D_SHADER_MODEL_6_2;
                #else
                shaderModel.HighestShaderModel = D3D_SHADER_MODEL_6_0;
                #endif
                HRESULT hr = device->CheckFeatureSupport(D3D12_FEATURE_SHADER_MODEL, &shaderModel, sizeof(shaderModel));
                while (hr == E_INVALIDARG && shaderModel.HighestShaderModel > D3D_SHADER_MODEL_6_0)
                {
                    shaderModel.HighestShaderModel = static_cast<D3D_SHADER_MODEL>(static_cast<int>(shaderModel.HighestShaderModel) - 1);
                    hr = device->CheckFeatureSupport(D3D12_FEATURE_SHADER_MODEL, &shaderModel, sizeof(shaderModel));
                }
                if (FAILED(hr))
                {
                    shaderModel.HighestShaderModel = D3D_SHADER_MODEL_5_1;
                }

                const wchar_t* shaderModelVer = L"Unknown";
                switch (shaderModel.HighestShaderModel)
                {
                case D3D_SHADER_MODEL_5_1: shaderModelVer = L"5.1"; break;
                case D3D_SHADER_MODEL_6_0: shaderModelVer = L"6.0"; break;

                #if defined(NTDDI_WIN10_RS3) && (NTDDI_VERSION >= NTDDI_WIN10_RS3)
                case D3D_SHADER_MODEL_6_1: shaderModelVer = L"6.1"; break;
                #endif

                #if defined(NTDDI_WIN10_RS4) && (NTDDI_VERSION >= NTDDI_WIN10_RS4)
                case D3D_SHADER_MODEL_6_2: shaderModelVer = L"6.2"; break;
                #endif

                #if defined(NTDDI_WIN10_RS5) && (NTDDI_VERSION >= NTDDI_WIN10_RS5)
                case D3D_SHADER_MODEL_6_3: shaderModelVer = L"6.3"; break;
                case D3D_SHADER_MODEL_6_4: shaderModelVer = L"6.4"; break;
                #endif

                #if defined(NTDDI_WIN10_19H1) && (NTDDI_VERSION >= NTDDI_WIN10_19H1)
                case D3D_SHADER_MODEL_6_5: shaderModelVer = L"6.5"; break;
                #endif

                #if defined(NTDDI_WIN10_VB) && (NTDDI_VERSION >= NTDDI_WIN10_VB)
                case D3D_SHADER_MODEL_6_6: shaderModelVer = L"6.6"; break;
                #endif

                #if defined(NTDDI_WIN10_FE) && (NTDDI_VERSION >= NTDDI_WIN10_FE)
                case D3D_SHADER_MODEL_6_7: shaderModelVer = L"6.7"; break;
                #endif
                }

                wchar_t buff[128] = {};
                swprintf_s(buff, L"%ls / %ls", shaderModelVer, rootSigVer);
                DrawStringLeft(m_batch.get(), m_smallFont.get(), L"Shader Model / Root Signature", left, y, m_scale);
                y += DrawStringRight(m_batch.get(), m_smallFont.get(), buff, right, y, m_scale);

                // Optional Direct3D 12 features
                D3D12_FEATURE_DATA_D3D12_OPTIONS d3d12opts = {};
                if (SUCCEEDED(device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS, &d3d12opts, sizeof(d3d12opts))))
                {
                    const wchar_t* tiledTier = L"Unknown";
                    switch (d3d12opts.TiledResourcesTier)
                    {
                    case D3D12_TILED_RESOURCES_TIER_NOT_SUPPORTED: tiledTier = L"Not supported"; break;
                    case D3D12_TILED_RESOURCES_TIER_1: tiledTier = L"Tier 1"; break;
                    case D3D12_TILED_RESOURCES_TIER_2: tiledTier = L"Tier 2"; break;
                    case D3D12_TILED_RESOURCES_TIER_3: tiledTier = L"Tier 3"; break;

                    #if defined(NTDDI_WIN10_RS4) && (NTDDI_VERSION >= NTDDI_WIN10_RS4)
                    case D3D12_TILED_RESOURCES_TIER_4: tiledTier = L"Tier 4"; break;
                    #endif
                    }

                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"TiledResourcesTier", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), tiledTier, right, y, m_scale);

                    const wchar_t* resourceTier = L"Unknown";
                    switch (d3d12opts.ResourceBindingTier)
                    {
                    case D3D12_RESOURCE_BINDING_TIER_1: resourceTier = L"Tier 1"; break;
                    case D3D12_RESOURCE_BINDING_TIER_2: resourceTier = L"Tier 2"; break;
                    case D3D12_RESOURCE_BINDING_TIER_3: resourceTier = L"Tier 3"; break;
                    }

                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"ResourceBindingTier", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), resourceTier, right, y, m_scale);

                    const wchar_t* cRastTier = L"Unknown";
                    switch (d3d12opts.ConservativeRasterizationTier)
                    {
                    case D3D12_CONSERVATIVE_RASTERIZATION_TIER_NOT_SUPPORTED: cRastTier = L"Not supported"; break;
                    case D3D12_CONSERVATIVE_RASTERIZATION_TIER_1: cRastTier = L"Tier 1"; break;
                    case D3D12_CONSERVATIVE_RASTERIZATION_TIER_2: cRastTier = L"Tier 2"; break;
                    case D3D12_CONSERVATIVE_RASTERIZATION_TIER_3: cRastTier = L"Tier 3"; break;
                    }

                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"ConservativeRasterizationTier", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), cRastTier, right, y, m_scale);

                    const wchar_t* heapTier = L"Unknown";
                    switch (d3d12opts.ResourceHeapTier)
                    {
                    case D3D12_RESOURCE_HEAP_TIER_1: heapTier = L"Tier 1"; break;
                    case D3D12_RESOURCE_HEAP_TIER_2: heapTier = L"Tier 2"; break;
                    }

                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"ResourceHeapTier", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), heapTier, right, y, m_scale);

                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"StandardSwizzle64KBSupported", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), d3d12opts.StandardSwizzle64KBSupported ? L"true" : L"false", right, y, m_scale);

                    const wchar_t* crossTier = L"Unknown";
                    switch (d3d12opts.CrossNodeSharingTier)
                    {
                    case D3D12_CROSS_NODE_SHARING_TIER_NOT_SUPPORTED: crossTier = L"Not supported"; break;
                    case D3D12_CROSS_NODE_SHARING_TIER_1_EMULATED: crossTier = L"Tier 1 (emulated)"; break;
                    case D3D12_CROSS_NODE_SHARING_TIER_1: crossTier = L"Tier 1"; break;
                    case D3D12_CROSS_NODE_SHARING_TIER_2: crossTier = L"Tier 2"; break;

                    #if defined(NTDDI_WIN10_RS4) && (NTDDI_VERSION >= NTDDI_WIN10_RS4)
                    case D3D12_CROSS_NODE_SHARING_TIER_3: crossTier = L"Tier 3"; break;
                    #endif
                    }

                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"CrossNodeSharingTier", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), crossTier, right, y, m_scale);

                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"CrossAdapterRowMajorTextureSupported", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), d3d12opts.CrossAdapterRowMajorTextureSupported ? L"true" : L"false", right, y, m_scale);

                    swprintf_s(buff, L"%u", d3d12opts.MaxGPUVirtualAddressBitsPerResource);
                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"MaxGPUVirtualAddressBitsPerResource", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), buff, right, y, m_scale);
                }
            }
        }
        break;

    case InfoPage::DIRECT3D12_OPT1:
        {
            y += DrawStringCenter(m_batch.get(), m_largeFont.get(), L"Direct3D 12 Optional Features", mid, y, ATG::Colors::LightGrey, m_scale);

            auto device = m_deviceResources->GetD3DDevice12();

            if (!device)
            {
                y += DrawStringCenter(m_batch.get(), m_smallFont.get(), L"Not supported", mid, y, ATG::Colors::Orange, m_scale);
            }
            else
            {
                // Optional Direct3D 12 features for Windows 10 Anniversary Update
                D3D12_FEATURE_DATA_D3D12_OPTIONS1 d3d12opts1 = {};
                if (SUCCEEDED(device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS1, &d3d12opts1, sizeof(d3d12opts1))))
                {
                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"WaveOps", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), d3d12opts1.WaveOps ? L"true" : L"false", right, y, m_scale);

                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"ExpandedComputeResourceStates", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), d3d12opts1.ExpandedComputeResourceStates ? L"true" : L"false", right, y, m_scale);

                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"Int64ShaderOps", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), d3d12opts1.Int64ShaderOps ? L"true" : L"false", right, y, m_scale);
                }

                // Optional Direct3D 12 features for Windows 10 Creators Update
                #if defined(NTDDI_WIN10_RS2) && (NTDDI_VERSION >= NTDDI_WIN10_RS2)
                D3D12_FEATURE_DATA_D3D12_OPTIONS2 d3d12opts2 = {};
                if (SUCCEEDED(device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS2, &d3d12opts2, sizeof(d3d12opts2))))
                {
                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"DepthBoundsTestSupported", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), d3d12opts2.DepthBoundsTestSupported ? L"true" : L"false", right, y, m_scale);

                    const wchar_t* psmpTier = L"Unknown";
                    switch (d3d12opts2.ProgrammableSamplePositionsTier)
                    {
                    case D3D12_PROGRAMMABLE_SAMPLE_POSITIONS_TIER_NOT_SUPPORTED: psmpTier = L"Not supported"; break;
                    case D3D12_PROGRAMMABLE_SAMPLE_POSITIONS_TIER_1: psmpTier = L"Tier 1"; break;
                    case D3D12_PROGRAMMABLE_SAMPLE_POSITIONS_TIER_2: psmpTier = L"Tier 2"; break;
                    }

                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"ProgrammableSamplePositionsTier", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), psmpTier, right, y, m_scale);
                }
                #endif

                // Optional Direct3D 12 features for Windows 10 Fall Creators Update
                #if defined(NTDDI_WIN10_RS3) && (NTDDI_VERSION >= NTDDI_WIN10_RS3)
                D3D12_FEATURE_DATA_D3D12_OPTIONS3 d3d12opts3 = {};
                if (SUCCEEDED(device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS3, &d3d12opts3, sizeof(d3d12opts3))))
                {
                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"CopyQueueTimestampQueriesSupported", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), d3d12opts3.CopyQueueTimestampQueriesSupported ? L"true" : L"false", right, y, m_scale);

                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"CastingFullyTypedFormatSupported", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), d3d12opts3.CastingFullyTypedFormatSupported ? L"true" : L"false", right, y, m_scale);

                    d3d12opts3.WriteBufferImmediateSupportFlags = static_cast<D3D12_COMMAND_LIST_SUPPORT_FLAGS>(0xFFFFFFFF);
                    wchar_t vbSupportFlags[128] = {};
                    if (d3d12opts3.WriteBufferImmediateSupportFlags & D3D12_COMMAND_LIST_SUPPORT_FLAG_DIRECT)
                    {
                        wcscat_s(vbSupportFlags, L"DIRECT ");
                    }
                    if (d3d12opts3.WriteBufferImmediateSupportFlags & D3D12_COMMAND_LIST_SUPPORT_FLAG_BUNDLE)
                    {
                        wcscat_s(vbSupportFlags, L"BUNDLE ");
                    }
                    if (d3d12opts3.WriteBufferImmediateSupportFlags & D3D12_COMMAND_LIST_SUPPORT_FLAG_COMPUTE)
                    {
                        wcscat_s(vbSupportFlags, L"COMPUTE ");
                    }
                    if (d3d12opts3.WriteBufferImmediateSupportFlags & D3D12_COMMAND_LIST_SUPPORT_FLAG_COPY)
                    {
                        wcscat_s(vbSupportFlags, L"COPY ");
                    }
                    if (d3d12opts3.WriteBufferImmediateSupportFlags & D3D12_COMMAND_LIST_SUPPORT_FLAG_VIDEO_DECODE)
                    {
                        wcscat_s(vbSupportFlags, L"VDECODE ");
                    }
                    if (d3d12opts3.WriteBufferImmediateSupportFlags & D3D12_COMMAND_LIST_SUPPORT_FLAG_VIDEO_PROCESS)
                    {
                        wcscat_s(vbSupportFlags, L"VPROCESS");
                    }
                    if (!*vbSupportFlags)
                    {
                        wcscat_s(vbSupportFlags, L"None");
                    }

                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"WriteBufferImmediateSupportFlags", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), vbSupportFlags, right, y, m_scale);

                    const wchar_t* vinstTier  = L"Unknown";
                    switch (d3d12opts3.ViewInstancingTier)
                    {
                    case D3D12_VIEW_INSTANCING_TIER_NOT_SUPPORTED: vinstTier = L"Not supported"; break;
                    case D3D12_VIEW_INSTANCING_TIER_1: vinstTier = L"Tier 1"; break;
                    case D3D12_VIEW_INSTANCING_TIER_2: vinstTier = L"Tier 2"; break;
                    case D3D12_VIEW_INSTANCING_TIER_3: vinstTier = L"Tier 3"; break;
                    }

                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"ViewInstancingTier", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), vinstTier, right, y, m_scale);

                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"BarycentricsSupported", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), d3d12opts3.BarycentricsSupported ? L"true" : L"false", right, y, m_scale);
                }

                D3D12_FEATURE_DATA_EXISTING_HEAPS d3d12heaps = {};
                if (SUCCEEDED(device->CheckFeatureSupport(D3D12_FEATURE_EXISTING_HEAPS, &d3d12heaps, sizeof(d3d12heaps))))
                {
                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"Existing Heaps", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), d3d12heaps.Supported ? L"true" : L"false", right, y, m_scale);
                }
                #endif
            }
        }
        break;

    case InfoPage::DIRECT3D12_OPT2:
        {
            y += DrawStringCenter(m_batch.get(), m_largeFont.get(), L"Direct3D 12 Optional Features (continued)", mid, y, ATG::Colors::LightGrey, m_scale);

            auto device = m_deviceResources->GetD3DDevice12();

            if (!device)
            {
                y += DrawStringCenter(m_batch.get(), m_smallFont.get(), L"Not supported", mid, y, ATG::Colors::Orange, m_scale);
            }
            else
            {
                bool found = false;

                // Optional Direct3D 12 features for Windows 10 April 2018 Update
                #if defined(NTDDI_WIN10_RS4) && (NTDDI_VERSION >= NTDDI_WIN10_RS4)
                D3D12_FEATURE_DATA_D3D12_OPTIONS4 d3d12opts4 = {};
                if (SUCCEEDED(device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS4, &d3d12opts4, sizeof(d3d12opts4))))
                {
                    found = true;

                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"MSAA64KBAlignedTextureSupported", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), d3d12opts4.MSAA64KBAlignedTextureSupported ? L"true" : L"false", right, y, m_scale);

                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"Native16BitShaderOpsSupported", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), d3d12opts4.Native16BitShaderOpsSupported ? L"true" : L"false", right, y, m_scale);

                    const wchar_t* srcompatTier = L"Unknown";
                    switch (d3d12opts4.SharedResourceCompatibilityTier)
                    {
                        case D3D12_SHARED_RESOURCE_COMPATIBILITY_TIER_0: srcompatTier = L"Tier 0"; break;
                        case D3D12_SHARED_RESOURCE_COMPATIBILITY_TIER_1: srcompatTier = L"Tier 1"; break;

                        #if defined(NTDDI_WIN10_VB) && (NTDDI_VERSION >= NTDDI_WIN10_VB)
                        case D3D12_SHARED_RESOURCE_COMPATIBILITY_TIER_2: srcompatTier = L"Tier 2"; break;
                        #endif
                    }

                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"SharedResourceCompatibilityTier", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), srcompatTier, right, y, m_scale);
                }

                D3D12_FEATURE_DATA_SERIALIZATION d3d12serial = {};
                if (SUCCEEDED(device->CheckFeatureSupport(D3D12_FEATURE_SERIALIZATION, &d3d12serial, sizeof(d3d12serial))))
                {
                    found = true;

                    const wchar_t* serialTier = L"Unknown";
                    switch (d3d12serial.HeapSerializationTier)
                    {
                        case D3D12_HEAP_SERIALIZATION_TIER_0: serialTier = L"Tier 0"; break;
                        case D3D12_HEAP_SERIALIZATION_TIER_10: serialTier = L"Tier 10"; break;
                    }

                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"HeapSerializationTier", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), serialTier, right, y, m_scale);

                    wchar_t buff[128] = {};
                    swprintf_s(buff, L"%u", d3d12serial.NodeIndex);
                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"Serialization NodeIndex", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), buff, right, y, m_scale);
                }

                D3D12_FEATURE_DATA_CROSS_NODE d3d12xnode = {};
                if (SUCCEEDED(device->CheckFeatureSupport(D3D12_FEATURE_CROSS_NODE, &d3d12xnode, sizeof(d3d12xnode))))
                {
                    found = true;

                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"Cross node AtomicShaderInstructions", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), d3d12xnode.AtomicShaderInstructions ? L"true" : L"false", right, y, m_scale);

                    const wchar_t* shareTier = L"Unknown";
                    switch (d3d12xnode.SharingTier)
                    {
                        case D3D12_CROSS_NODE_SHARING_TIER_NOT_SUPPORTED: shareTier = L"Not supported"; break;
                        case D3D12_CROSS_NODE_SHARING_TIER_1_EMULATED: shareTier = L"Tier 1 (Emulated)"; break;
                        case D3D12_CROSS_NODE_SHARING_TIER_1: shareTier = L"Tier 1"; break;
                        case D3D12_CROSS_NODE_SHARING_TIER_2: shareTier = L"Tier 2"; break;
                        case D3D12_CROSS_NODE_SHARING_TIER_3: shareTier = L"Tier 3"; break;
                    }

                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"Cross node SharingTier", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), shareTier, right, y, m_scale);
                }
                #endif

                // Optional Direct3D 12 features for Windows 10 October 2018 Update
                #if defined(NTDDI_WIN10_RS5) && (NTDDI_VERSION >= NTDDI_WIN10_RS5)
                D3D12_FEATURE_DATA_D3D12_OPTIONS5 d3d12opts5 = {};
                if (SUCCEEDED(device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS5, &d3d12opts5, sizeof(d3d12opts5))))
                {
                    found = true;

                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"SRVOnlyTiledResourceTier3", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), d3d12opts5.SRVOnlyTiledResourceTier3 ? L"true" : L"false", right, y, m_scale);

                    const wchar_t* passTier = L"Unknown";
                    switch (d3d12opts5.RenderPassesTier)
                    {
                    case D3D12_RENDER_PASS_TIER_0: passTier = L"Tier 0"; break;
                    case D3D12_RENDER_PASS_TIER_1: passTier = L"Tier 1"; break;
                    case D3D12_RENDER_PASS_TIER_2: passTier = L"Tier 2"; break;
                    }

                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"RenderPassesTier", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), passTier, right, y, m_scale);

                    const wchar_t* rtTier = L"Unknown";
                    switch (d3d12opts5.RaytracingTier)
                    {
                    case D3D12_RAYTRACING_TIER_NOT_SUPPORTED: rtTier = L"Not Supported"; break;
                    case D3D12_RAYTRACING_TIER_1_0: rtTier = L"Tier 1"; break;

                    #if defined(NTDDI_WIN10_VB) && (NTDDI_VERSION >= NTDDI_WIN10_VB)
                    case D3D12_RAYTRACING_TIER_1_1: rtTier = L"Tier 1.1"; break;
                    #endif
                    }

                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"RaytracingTier", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), rtTier, right, y, m_scale);
                }
                #endif

                if (!found)
                {
                    y += DrawStringCenter(m_batch.get(), m_smallFont.get(), L"Requires Windows 10 April 2018 Update or later", mid, y, ATG::Colors::Orange, m_scale);
                }
            }
        }
        break;

    case InfoPage::DIRECT3D12_OPT3:
        {
            y += DrawStringCenter(m_batch.get(), m_largeFont.get(), L"Direct3D 12 Optional Features (continued)", mid, y, ATG::Colors::LightGrey, m_scale);

            auto device = m_deviceResources->GetD3DDevice12();

            if (!device)
            {
                y += DrawStringCenter(m_batch.get(), m_smallFont.get(), L"Not supported", mid, y, ATG::Colors::Orange, m_scale);
            }
            else
            {
                bool found = false;

                // Optional Direct3D 12 features for Windows 10 May 2019 Update
                #if defined(NTDDI_WIN10_19H1) && (NTDDI_VERSION >= NTDDI_WIN10_19H1)
                D3D12_FEATURE_DATA_D3D12_OPTIONS6 d3d12opts6 = {};
                if (SUCCEEDED(device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS6, &d3d12opts6, sizeof(d3d12opts6))))
                {
                    found = true;

                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"AdditionalShadingRatesSupported", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), d3d12opts6.AdditionalShadingRatesSupported ? L"true" : L"false", right, y, m_scale);

                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"PerPrimitiveShadingRateSupportedWithViewportIndexing", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), d3d12opts6.PerPrimitiveShadingRateSupportedWithViewportIndexing ? L"true" : L"false", right, y, m_scale);

                    const wchar_t* vrsTier = L"Unknown";
                    switch (d3d12opts6.VariableShadingRateTier)
                    {
                    case D3D12_VARIABLE_SHADING_RATE_TIER_NOT_SUPPORTED: vrsTier = L"Not Supported"; break;
                    case D3D12_VARIABLE_SHADING_RATE_TIER_1: vrsTier = L"Tier 1"; break;
                    case D3D12_VARIABLE_SHADING_RATE_TIER_2: vrsTier = L"Tier 2"; break;
                    }

                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"VariableShadingRateTier", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), vrsTier, right, y, m_scale);

                    wchar_t buff[128] = {};
                    swprintf_s(buff, L"%u", d3d12opts6.ShadingRateImageTileSize);
                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"ShadingRateImageTileSize", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), buff, right, y, m_scale);

                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"BackgroundProcessingSupported", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), d3d12opts6.BackgroundProcessingSupported ? L"true" : L"false", right, y, m_scale);
                }
                #endif

                // Optional Direct3D 12 features for Windows 10 "20H1" Update
                #if defined(NTDDI_WIN10_VB) && (NTDDI_VERSION >= NTDDI_WIN10_VB)
                D3D12_FEATURE_DATA_D3D12_OPTIONS7 d3d12opts7 = {};
                if (SUCCEEDED(device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS7, &d3d12opts7, sizeof(d3d12opts7))))
                {
                    found = true;

                    // https://devblogs.microsoft.com/directx/coming-to-directx-12-more-control-over-memory-allocation/
                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"D3D12_HEAP_FLAG_CREATE_NOT_RESIDENT", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), L"true", right, y, m_scale);

                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"D3D12_HEAP_FLAG_CREATE_NOT_ZEROED", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), L"true", right, y, m_scale);


                    const wchar_t* msTier = L"Unknown";
                    switch (d3d12opts7.MeshShaderTier)
                    {
                    case D3D12_MESH_SHADER_TIER_NOT_SUPPORTED: msTier = L"Not Supported"; break;
                    case D3D12_MESH_SHADER_TIER_1: msTier = L"Tier 1"; break;
                    }

                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"MeshShaderTier", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), msTier, right, y, m_scale);

                    const wchar_t* sfTier = L"Unknown";
                    switch (d3d12opts7.SamplerFeedbackTier)
                    {
                    case D3D12_SAMPLER_FEEDBACK_TIER_NOT_SUPPORTED: sfTier = L"Not Supported"; break;
                    case D3D12_SAMPLER_FEEDBACK_TIER_0_9: sfTier = L"Tier 0.9"; break;
                    case D3D12_SAMPLER_FEEDBACK_TIER_1_0: sfTier = L"Tier 1.0"; break;
                    }

                    DrawStringLeft(m_batch.get(), m_smallFont.get(), L"SamplerFeedbackTier", left, y, m_scale);
                    y += DrawStringRight(m_batch.get(), m_smallFont.get(), sfTier, right, y, m_scale);
                }
                #endif

                if (!found)
                {
                    y += DrawStringCenter(m_batch.get(), m_smallFont.get(), L"Requires Windows 10 May 2019 Update or later", mid, y, ATG::Colors::Orange, m_scale);
                }
            }
        }
        break;
    }

    m_batch->End();

    PIXEndEvent(context);

    // Show the new frame.
    PIXBeginEvent(PIX_COLOR_DEFAULT, L"Present");
    m_deviceResources->Present();
    PIXEndEvent();
}