Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#define INITGUID // set before devguid.h
#include "gfxWindowsPlatform.h"
#include "cairo.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/layers/CompositorBridgeChild.h"
#include "gfxBlur.h"
#include "gfxImageSurface.h"
#include "gfxWindowsSurface.h"
#include "nsUnicharUtils.h"
#include "nsUnicodeProperties.h"
#include "mozilla/Preferences.h"
#include "mozilla/ProfilerLabels.h"
#include "mozilla/ProfilerThreadSleep.h"
#include "mozilla/Components.h"
#include "mozilla/Sprintf.h"
#include "mozilla/WindowsVersion.h"
#include "nsIGfxInfo.h"
#include "nsServiceManagerUtils.h"
#include "nsTArray.h"
#include "nsThreadUtils.h"
#include "mozilla/Telemetry.h"
#include "plbase64.h"
#include "nsIXULRuntime.h"
#include "imgLoader.h"
#include "nsIGfxInfo.h"
#include "gfxCrashReporterUtils.h"
#include "gfxGDIFontList.h"
#include "gfxGDIFont.h"
#include "mozilla/layers/CanvasChild.h"
#include "mozilla/layers/CompositorThread.h"
#include "gfxDWriteFontList.h"
#include "gfxDWriteFonts.h"
#include "gfxDWriteCommon.h"
#include <dwrite.h>
#include "gfxTextRun.h"
#include "gfxUserFontSet.h"
#include "nsWindowsHelpers.h"
#include "gfx2DGlue.h"
#include <string>
#include <d3d10_1.h>
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/gfxVars.h"
#include <dwmapi.h>
#include <d3d11.h>
#include <d2d1_1.h>
#include "nsIMemoryReporter.h"
#include <winternl.h>
#include "d3dkmtQueryStatistics.h"
#include "base/thread.h"
#include "mozilla/StaticPrefs_gfx.h"
#include "mozilla/StaticPrefs_layers.h"
#include "gfxConfig.h"
#include "VsyncSource.h"
#include "DriverCrashGuard.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/gfx/DeviceManagerDx.h"
#include "mozilla/gfx/DisplayConfigWindows.h"
#include "mozilla/layers/DeviceAttachmentsD3D11.h"
#include "mozilla/WindowsProcessMitigations.h"
#include "D3D11Checks.h"
#include "mozilla/ScreenHelperWin.h"
using namespace mozilla;
using namespace mozilla::gfx;
using namespace mozilla::layers;
using namespace mozilla::widget;
using namespace mozilla::image;
using namespace mozilla::unicode;
DCForMetrics::DCForMetrics() {
// Get the whole screen DC:
mDC = GetDC(nullptr);
SetGraphicsMode(mDC, GM_ADVANCED);
}
class GfxD2DVramReporter final : public nsIMemoryReporter {
~GfxD2DVramReporter() {}
public:
NS_DECL_ISUPPORTS
NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
nsISupports* aData, bool aAnonymize) override {
MOZ_COLLECT_REPORT("gfx-d2d-vram-draw-target", KIND_OTHER, UNITS_BYTES,
Factory::GetD2DVRAMUsageDrawTarget(),
"Video memory used by D2D DrawTargets.");
MOZ_COLLECT_REPORT("gfx-d2d-vram-source-surface", KIND_OTHER, UNITS_BYTES,
Factory::GetD2DVRAMUsageSourceSurface(),
"Video memory used by D2D SourceSurfaces.");
return NS_OK;
}
};
NS_IMPL_ISUPPORTS(GfxD2DVramReporter, nsIMemoryReporter)
class GPUAdapterReporter final : public nsIMemoryReporter {
// Callers must Release the DXGIAdapter after use or risk mem-leak
static bool GetDXGIAdapter(IDXGIAdapter** aDXGIAdapter) {
RefPtr<ID3D11Device> d3d11Device;
RefPtr<IDXGIDevice> dxgiDevice;
bool result = false;
if ((d3d11Device = mozilla::gfx::Factory::GetDirect3D11Device())) {
if (d3d11Device->QueryInterface(__uuidof(IDXGIDevice),
getter_AddRefs(dxgiDevice)) == S_OK) {
result = (dxgiDevice->GetAdapter(aDXGIAdapter) == S_OK);
}
}
return result;
}
~GPUAdapterReporter() {}
public:
NS_DECL_ISUPPORTS
NS_IMETHOD
CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData,
bool aAnonymize) override {
HANDLE ProcessHandle = GetCurrentProcess();
int64_t dedicatedBytesUsed = 0;
int64_t sharedBytesUsed = 0;
int64_t committedBytesUsed = 0;
IDXGIAdapter* DXGIAdapter;
HMODULE gdi32Handle;
PFND3DKMTQS queryD3DKMTStatistics = nullptr;
if ((gdi32Handle = LoadLibrary(TEXT("gdi32.dll"))))
queryD3DKMTStatistics =
(PFND3DKMTQS)GetProcAddress(gdi32Handle, "D3DKMTQueryStatistics");
if (queryD3DKMTStatistics && GetDXGIAdapter(&DXGIAdapter)) {
// Most of this block is understood thanks to wj32's work on Process
// Hacker
DXGI_ADAPTER_DESC adapterDesc;
D3DKMTQS queryStatistics;
DXGIAdapter->GetDesc(&adapterDesc);
DXGIAdapter->Release();
memset(&queryStatistics, 0, sizeof(D3DKMTQS));
queryStatistics.Type = D3DKMTQS_PROCESS;
queryStatistics.AdapterLuid = adapterDesc.AdapterLuid;
queryStatistics.hProcess = ProcessHandle;
if (NT_SUCCESS(queryD3DKMTStatistics(&queryStatistics))) {
committedBytesUsed =
queryStatistics.QueryResult.ProcessInfo.SystemMemory.BytesAllocated;
}
memset(&queryStatistics, 0, sizeof(D3DKMTQS));
queryStatistics.Type = D3DKMTQS_ADAPTER;
queryStatistics.AdapterLuid = adapterDesc.AdapterLuid;
if (NT_SUCCESS(queryD3DKMTStatistics(&queryStatistics))) {
ULONG i;
ULONG segmentCount = queryStatistics.QueryResult.AdapterInfo.NbSegments;
for (i = 0; i < segmentCount; i++) {
memset(&queryStatistics, 0, sizeof(D3DKMTQS));
queryStatistics.Type = D3DKMTQS_SEGMENT;
queryStatistics.AdapterLuid = adapterDesc.AdapterLuid;
queryStatistics.QuerySegment.SegmentId = i;
if (NT_SUCCESS(queryD3DKMTStatistics(&queryStatistics))) {
bool aperture = queryStatistics.QueryResult.SegmentInfo.Aperture;
memset(&queryStatistics, 0, sizeof(D3DKMTQS));
queryStatistics.Type = D3DKMTQS_PROCESS_SEGMENT;
queryStatistics.AdapterLuid = adapterDesc.AdapterLuid;
queryStatistics.hProcess = ProcessHandle;
queryStatistics.QueryProcessSegment.SegmentId = i;
if (NT_SUCCESS(queryD3DKMTStatistics(&queryStatistics))) {
ULONGLONG bytesCommitted =
queryStatistics.QueryResult.ProcessSegmentInfo.BytesCommitted;
if (aperture)
sharedBytesUsed += bytesCommitted;
else
dedicatedBytesUsed += bytesCommitted;
}
}
}
}
}
FreeLibrary(gdi32Handle);
MOZ_COLLECT_REPORT("gpu-committed", KIND_OTHER, UNITS_BYTES,
committedBytesUsed,
"Memory committed by the Windows graphics system.");
MOZ_COLLECT_REPORT(
"gpu-dedicated", KIND_OTHER, UNITS_BYTES, dedicatedBytesUsed,
"Out-of-process memory allocated for this process in a physical "
"GPU adapter's memory.");
MOZ_COLLECT_REPORT("gpu-shared", KIND_OTHER, UNITS_BYTES, sharedBytesUsed,
"In-process memory that is shared with the GPU.");
return NS_OK;
}
};
NS_IMPL_ISUPPORTS(GPUAdapterReporter, nsIMemoryReporter)
Atomic<size_t> gfxWindowsPlatform::sD3D11SharedTextures;
Atomic<size_t> gfxWindowsPlatform::sD3D9SharedTextures;
class D3DSharedTexturesReporter final : public nsIMemoryReporter {
~D3DSharedTexturesReporter() {}
public:
NS_DECL_ISUPPORTS
NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
nsISupports* aData, bool aAnonymize) override {
if (gfxWindowsPlatform::sD3D11SharedTextures > 0) {
MOZ_COLLECT_REPORT("d3d11-shared-textures", KIND_OTHER, UNITS_BYTES,
gfxWindowsPlatform::sD3D11SharedTextures,
"D3D11 shared textures.");
}
if (gfxWindowsPlatform::sD3D9SharedTextures > 0) {
MOZ_COLLECT_REPORT("d3d9-shared-textures", KIND_OTHER, UNITS_BYTES,
gfxWindowsPlatform::sD3D9SharedTextures,
"D3D9 shared textures.");
}
return NS_OK;
}
};
NS_IMPL_ISUPPORTS(D3DSharedTexturesReporter, nsIMemoryReporter)
gfxWindowsPlatform::gfxWindowsPlatform() : mRenderMode(RENDER_GDI) {
// If win32k is locked down then we can't use COM STA and shouldn't need it.
// Also, we won't be using any GPU memory in this process.
if (!IsWin32kLockedDown()) {
/*
* Initialize COM
*/
CoInitialize(nullptr);
RegisterStrongMemoryReporter(new GfxD2DVramReporter());
RegisterStrongMemoryReporter(new GPUAdapterReporter());
RegisterStrongMemoryReporter(new D3DSharedTexturesReporter());
}
}
gfxWindowsPlatform::~gfxWindowsPlatform() {
mozilla::gfx::Factory::D2DCleanup();
DeviceManagerDx::Shutdown();
// We don't initialize COM when win32k is locked down.
if (!IsWin32kLockedDown()) {
/*
* Uninitialize COM
*/
CoUninitialize();
}
}
/* static */
void gfxWindowsPlatform::InitMemoryReportersForGPUProcess() {
MOZ_RELEASE_ASSERT(XRE_IsGPUProcess());
RegisterStrongMemoryReporter(new GfxD2DVramReporter());
RegisterStrongMemoryReporter(new GPUAdapterReporter());
RegisterStrongMemoryReporter(new D3DSharedTexturesReporter());
}
/* static */
nsresult gfxWindowsPlatform::GetGpuTimeSinceProcessStartInMs(
uint64_t* aResult) {
// If win32k is locked down then we should not have any GPU processing and
// cannot use these APIs either way.
if (IsWin32kLockedDown()) {
*aResult = 0;
return NS_OK;
}
nsModuleHandle module(LoadLibrary(L"gdi32.dll"));
if (!module) {
return NS_ERROR_NOT_AVAILABLE;
}
PFND3DKMTQS queryD3DKMTStatistics =
(PFND3DKMTQS)GetProcAddress(module, "D3DKMTQueryStatistics");
if (!queryD3DKMTStatistics) {
return NS_ERROR_NOT_AVAILABLE;
}
gfx::DeviceManagerDx* dm = DeviceManagerDx::Get();
if (!dm) {
return NS_ERROR_NOT_AVAILABLE;
}
D3D11DeviceStatus status;
if (!dm->ExportDeviceInfo(&status)) {
// Assume that we used 0ms of GPU time if the device manager
// doesn't know the device status.
*aResult = 0;
return NS_OK;
}
const DxgiAdapterDesc& adapterDesc = status.adapter();
D3DKMTQS queryStatistics;
memset(&queryStatistics, 0, sizeof(D3DKMTQS));
queryStatistics.Type = D3DKMTQS_ADAPTER;
queryStatistics.AdapterLuid = adapterDesc.AdapterLuid;
if (!NT_SUCCESS(queryD3DKMTStatistics(&queryStatistics))) {
return NS_ERROR_FAILURE;
}
uint64_t result = 0;
ULONG nodeCount = queryStatistics.QueryResult.AdapterInfo.NodeCount;
for (ULONG i = 0; i < nodeCount; ++i) {
memset(&queryStatistics, 0, sizeof(D3DKMTQS));
queryStatistics.Type = D3DKMTQS_PROCESS_NODE;
queryStatistics.AdapterLuid = adapterDesc.AdapterLuid;
queryStatistics.hProcess = GetCurrentProcess();
queryStatistics.QueryProcessNode.NodeId = i;
if (NT_SUCCESS(queryD3DKMTStatistics(&queryStatistics))) {
result += queryStatistics.QueryResult.ProcessNodeInformation.RunningTime
.QuadPart *
100 / PR_NSEC_PER_MSEC;
}
}
*aResult = result;
return NS_OK;
}
static void UpdateANGLEConfig() {
if (!gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) {
gfxConfig::Disable(Feature::D3D11_HW_ANGLE, FeatureStatus::Disabled,
"D3D11 compositing is disabled",
"FEATURE_FAILURE_HW_ANGLE_D3D11_DISABLED"_ns);
}
}
void gfxWindowsPlatform::InitAcceleration() {
gfxPlatform::InitAcceleration();
DeviceManagerDx::Init();
// Content processes should have received content device data from parent.
MOZ_ASSERT_IF(XRE_IsContentProcess(), GetInitContentDeviceData());
InitializeConfig();
InitGPUProcessSupport();
// Ensure devices initialization. SharedSurfaceANGLE and
// SharedSurfaceD3D11Interop use them. The devices are lazily initialized
// with WebRender to reduce memory usage.
// Initialize them now when running non-e10s.
if (!BrowserTabsRemoteAutostart()) {
EnsureDevicesInitialized();
}
UpdateANGLEConfig();
UpdateRenderMode();
// If we have Skia and we didn't init dwrite already, do it now.
if (!DWriteEnabled() && GetDefaultContentBackend() == BackendType::SKIA) {
InitDWriteSupport();
}
// We need to listen for font setting changes even if DWrite is not used.
Factory::SetSystemTextQuality(gfxVars::SystemTextQuality());
gfxVars::SetSystemTextQualityListener(
gfxDWriteFont::SystemTextQualityChanged);
// CanUseHardwareVideoDecoding depends on DeviceManagerDx state,
// so update the cached value now.
UpdateCanUseHardwareVideoDecoding();
// Our ScreenHelperWin also depends on DeviceManagerDx state.
if (XRE_IsParentProcess() && !gfxPlatform::IsHeadless()) {
ScreenHelperWin::RefreshScreens();
}
RecordStartupTelemetry();
}
void gfxWindowsPlatform::InitWebRenderConfig() {
gfxPlatform::InitWebRenderConfig();
UpdateBackendPrefs();
}
bool gfxWindowsPlatform::CanUseHardwareVideoDecoding() {
DeviceManagerDx* dm = DeviceManagerDx::Get();
if (!dm) {
return false;
}
if (!dm->TextureSharingWorks()) {
return false;
}
return !dm->IsWARP() && gfxPlatform::CanUseHardwareVideoDecoding();
}
bool gfxWindowsPlatform::InitDWriteSupport() {
mozilla::ScopedGfxFeatureReporter reporter("DWrite");
if (!gfxDWriteFont::InitDWriteSupport()) {
return false;
}
reporter.SetSuccessful();
return true;
}
bool gfxWindowsPlatform::HandleDeviceReset() {
DeviceResetReason resetReason = DeviceResetReason::OK;
if (!DidRenderingDeviceReset(&resetReason)) {
return false;
}
if (resetReason != DeviceResetReason::FORCED_RESET) {
Telemetry::Accumulate(Telemetry::DEVICE_RESET_REASON,
uint32_t(resetReason));
}
// Remove devices and adapters.
DeviceManagerDx::Get()->ResetDevices();
imgLoader::NormalLoader()->ClearCache(true);
imgLoader::NormalLoader()->ClearCache(false);
imgLoader::PrivateBrowsingLoader()->ClearCache(true);
imgLoader::PrivateBrowsingLoader()->ClearCache(false);
gfxAlphaBoxBlur::ShutdownBlurCache();
gfxConfig::Reset(Feature::D3D11_COMPOSITING);
gfxConfig::Reset(Feature::D3D11_HW_ANGLE);
gfxConfig::Reset(Feature::DIRECT2D);
InitializeConfig();
// XXX Add InitWebRenderConfig() calling.
if (mInitializedDevices) {
InitGPUProcessSupport();
InitializeDevices();
}
UpdateANGLEConfig();
return true;
}
BackendPrefsData gfxWindowsPlatform::GetBackendPrefs() const {
BackendPrefsData data;
data.mCanvasBitmask = BackendTypeBit(BackendType::SKIA);
data.mContentBitmask = BackendTypeBit(BackendType::SKIA);
data.mCanvasDefault = BackendType::SKIA;
data.mContentDefault = BackendType::SKIA;
if (gfxConfig::IsEnabled(Feature::DIRECT2D)) {
data.mCanvasBitmask |= BackendTypeBit(BackendType::DIRECT2D1_1);
data.mCanvasDefault = BackendType::DIRECT2D1_1;
}
return data;
}
void gfxWindowsPlatform::UpdateBackendPrefs() {
BackendPrefsData data = GetBackendPrefs();
// Remove DIRECT2D1 preference if D2D1Device does not exist.
if (!Factory::HasD2D1Device()) {
data.mContentBitmask &= ~BackendTypeBit(BackendType::DIRECT2D1_1);
if (data.mContentDefault == BackendType::DIRECT2D1_1) {
data.mContentDefault = BackendType::SKIA;
}
// Don't exclude DIRECT2D1_1 if using remote canvas, because DIRECT2D1_1 and
// hence the device will be used in the GPU process.
if (!gfxPlatform::UseRemoteCanvas()) {
data.mCanvasBitmask &= ~BackendTypeBit(BackendType::DIRECT2D1_1);
if (data.mCanvasDefault == BackendType::DIRECT2D1_1) {
data.mCanvasDefault = BackendType::SKIA;
}
}
}
InitBackendPrefs(std::move(data));
}
bool gfxWindowsPlatform::IsDirect2DBackend() {
return GetDefaultContentBackend() == BackendType::DIRECT2D1_1;
}
void gfxWindowsPlatform::UpdateRenderMode() {
bool didReset = HandleDeviceReset();
UpdateBackendPrefs();
if (didReset) {
mScreenReferenceDrawTarget = CreateOffscreenContentDrawTarget(
IntSize(1, 1), SurfaceFormat::B8G8R8A8);
if (!mScreenReferenceDrawTarget) {
gfxCriticalNote
<< "Failed to update reference draw target after device reset"
<< ", D3D11 device:" << hexa(Factory::GetDirect3D11Device().get())
<< ", D3D11 status:"
<< FeatureStatusToString(
gfxConfig::GetValue(Feature::D3D11_COMPOSITING))
<< ", D2D1 device:" << hexa(Factory::GetD2D1Device().get())
<< ", D2D1 status:"
<< FeatureStatusToString(gfxConfig::GetValue(Feature::DIRECT2D))
<< ", content:" << int(GetDefaultContentBackend())
<< ", compositor:" << int(GetCompositorBackend());
MOZ_CRASH(
"GFX: Failed to update reference draw target after device reset");
}
}
}
mozilla::gfx::BackendType gfxWindowsPlatform::GetContentBackendFor(
mozilla::layers::LayersBackend aLayers) {
mozilla::gfx::BackendType defaultBackend =
gfxPlatform::GetDefaultContentBackend();
if (aLayers == LayersBackend::LAYERS_WR &&
gfx::gfxVars::UseWebRenderANGLE()) {
return defaultBackend;
}
if (defaultBackend == BackendType::DIRECT2D1_1) {
// We can't have D2D without D3D11 layers, so fallback to Skia.
return BackendType::SKIA;
}
// Otherwise we have some non-accelerated backend and that's ok.
return defaultBackend;
}
mozilla::gfx::BackendType gfxWindowsPlatform::GetPreferredCanvasBackend() {
mozilla::gfx::BackendType backend = gfxPlatform::GetPreferredCanvasBackend();
if (backend == BackendType::DIRECT2D1_1) {
if (!gfx::gfxVars::UseWebRenderANGLE()) {
// We can't have D2D without ANGLE when WebRender is enabled, so fallback
// to Skia.
return BackendType::SKIA;
}
// Fall back to software when remote canvas has been deactivated.
if (CanvasChild::Deactivated()) {
return BackendType::SKIA;
}
}
return backend;
}
bool gfxWindowsPlatform::CreatePlatformFontList() {
if (DWriteEnabled()) {
if (gfxPlatformFontList::Initialize(new gfxDWriteFontList)) {
return true;
}
// DWrite font initialization failed! Don't know why this would happen,
// but apparently it can - see bug 594865.
// So we're going to fall back to GDI fonts & rendering.
DisableD2D(FeatureStatus::Failed, "Failed to initialize fonts",
"FEATURE_FAILURE_FONT_FAIL"_ns);
}
// Make sure the static variable is initialized...
gfxPlatform::HasVariationFontSupport();
// ...then force it to false, even if the Windows version was recent enough
// to permit it, as we're using GDI fonts.
sHasVariationFontSupport = false;
return gfxPlatformFontList::Initialize(new gfxGDIFontList);
}
// This function will permanently disable D2D for the session. It's intended to
// be used when, after initially chosing to use Direct2D, we encounter a
// scenario we can't support.
//
// This is called during gfxPlatform::Init() so at this point there should be no
// DrawTargetD2D/1 instances.
void gfxWindowsPlatform::DisableD2D(FeatureStatus aStatus, const char* aMessage,
const nsACString& aFailureId) {
gfxConfig::SetFailed(Feature::DIRECT2D, aStatus, aMessage, aFailureId);
Factory::SetDirect3D11Device(nullptr);
UpdateBackendPrefs();
}
already_AddRefed<gfxASurface> gfxWindowsPlatform::CreateOffscreenSurface(
const IntSize& aSize, gfxImageFormat aFormat) {
if (!Factory::AllowedSurfaceSize(aSize)) {
return nullptr;
}
RefPtr<gfxASurface> surf = nullptr;
#ifdef CAIRO_HAS_WIN32_SURFACE
if (!XRE_IsContentProcess()) {
if (mRenderMode == RENDER_GDI || mRenderMode == RENDER_DIRECT2D) {
surf = new gfxWindowsSurface(aSize, aFormat);
}
}
#endif
if (!surf || surf->CairoStatus()) {
surf = new gfxImageSurface(aSize, aFormat);
}
return surf.forget();
}
static const char kFontAparajita[] = "Aparajita";
static const char kFontArabicTypesetting[] = "Arabic Typesetting";
static const char kFontArial[] = "Arial";
static const char kFontArialUnicodeMS[] = "Arial Unicode MS";
static const char kFontCambria[] = "Cambria";
static const char kFontCambriaMath[] = "Cambria Math";
static const char kFontEbrima[] = "Ebrima";
static const char kFontEstrangeloEdessa[] = "Estrangelo Edessa";
static const char kFontEuphemia[] = "Euphemia";
static const char kFontGabriola[] = "Gabriola";
static const char kFontJavaneseText[] = "Javanese Text";
static const char kFontKhmerUI[] = "Khmer UI";
static const char kFontLaoUI[] = "Lao UI";
static const char kFontLeelawadeeUI[] = "Leelawadee UI";
static const char kFontLucidaSansUnicode[] = "Lucida Sans Unicode";
static const char kFontMVBoli[] = "MV Boli";
static const char kFontMalgunGothic[] = "Malgun Gothic";
static const char kFontMicrosoftJhengHei[] = "Microsoft JhengHei";
static const char kFontMicrosoftNewTaiLue[] = "Microsoft New Tai Lue";
static const char kFontMicrosoftPhagsPa[] = "Microsoft PhagsPa";
static const char kFontMicrosoftTaiLe[] = "Microsoft Tai Le";
static const char kFontMicrosoftUighur[] = "Microsoft Uighur";
static const char kFontMicrosoftYaHei[] = "Microsoft YaHei";
static const char kFontMicrosoftYiBaiti[] = "Microsoft Yi Baiti";
static const char kFontMeiryo[] = "Meiryo";
static const char kFontMongolianBaiti[] = "Mongolian Baiti";
static const char kFontMyanmarText[] = "Myanmar Text";
static const char kFontNirmalaUI[] = "Nirmala UI";
static const char kFontNyala[] = "Nyala";
static const char kFontPlantagenetCherokee[] = "Plantagenet Cherokee";
static const char kFontSegoeUI[] = "Segoe UI";
static const char kFontSegoeUIEmoji[] = "Segoe UI Emoji";
static const char kFontSegoeUISymbol[] = "Segoe UI Symbol";
static const char kFontSylfaen[] = "Sylfaen";
static const char kFontTraditionalArabic[] = "Traditional Arabic";
static const char kFontTwemojiMozilla[] = "Twemoji Mozilla";
static const char kFontUtsaah[] = "Utsaah";
static const char kFontYuGothic[] = "Yu Gothic";
void gfxWindowsPlatform::GetCommonFallbackFonts(
uint32_t aCh, Script aRunScript, eFontPresentation aPresentation,
nsTArray<const char*>& aFontList) {
if (PrefersColor(aPresentation)) {
aFontList.AppendElement(kFontSegoeUIEmoji);
aFontList.AppendElement(kFontTwemojiMozilla);
}
// Arial is used as the default fallback for system fallback
aFontList.AppendElement(kFontArial);
if (!IS_IN_BMP(aCh)) {
uint32_t p = aCh >> 16;
if (p == 1) { // SMP plane
aFontList.AppendElement(kFontSegoeUISymbol);
aFontList.AppendElement(kFontEbrima);
aFontList.AppendElement(kFontNirmalaUI);
aFontList.AppendElement(kFontCambriaMath);
}
} else {
uint32_t b = (aCh >> 8) & 0xff;
switch (b) {
case 0x05:
aFontList.AppendElement(kFontEstrangeloEdessa);
aFontList.AppendElement(kFontCambria);
break;
case 0x06:
aFontList.AppendElement(kFontMicrosoftUighur);
break;
case 0x07:
aFontList.AppendElement(kFontEstrangeloEdessa);
aFontList.AppendElement(kFontMVBoli);
aFontList.AppendElement(kFontEbrima);
break;
case 0x09:
aFontList.AppendElement(kFontNirmalaUI);
aFontList.AppendElement(kFontUtsaah);
aFontList.AppendElement(kFontAparajita);
break;
case 0x0a:
case 0x0b:
case 0x0c:
case 0x0d:
aFontList.AppendElement(kFontNirmalaUI);
break;
case 0x0e:
aFontList.AppendElement(kFontLaoUI);
aFontList.AppendElement(kFontLeelawadeeUI);
break;
case 0x10:
aFontList.AppendElement(kFontMyanmarText);
break;
case 0x11:
aFontList.AppendElement(kFontMalgunGothic);
break;
case 0x12:
case 0x13:
aFontList.AppendElement(kFontNyala);
aFontList.AppendElement(kFontPlantagenetCherokee);
break;
case 0x14:
case 0x15:
case 0x16:
aFontList.AppendElement(kFontEuphemia);
aFontList.AppendElement(kFontSegoeUISymbol);
break;
case 0x17:
aFontList.AppendElement(kFontKhmerUI);
aFontList.AppendElement(kFontLeelawadeeUI);
break;
case 0x18: // Mongolian
aFontList.AppendElement(kFontMongolianBaiti);
aFontList.AppendElement(kFontEuphemia);
break;
case 0x19:
aFontList.AppendElement(kFontMicrosoftTaiLe);
aFontList.AppendElement(kFontMicrosoftNewTaiLue);
aFontList.AppendElement(kFontKhmerUI);
aFontList.AppendElement(kFontLeelawadeeUI);
break;
case 0x1a:
aFontList.AppendElement(kFontLeelawadeeUI);
break;
case 0x1c:
aFontList.AppendElement(kFontNirmalaUI);
break;
case 0x20: // Symbol ranges
case 0x21:
case 0x22:
case 0x23:
case 0x24:
case 0x25:
case 0x26:
case 0x27:
case 0x29:
case 0x2a:
case 0x2b:
case 0x2c:
aFontList.AppendElement(kFontSegoeUI);
aFontList.AppendElement(kFontSegoeUISymbol);
aFontList.AppendElement(kFontCambria);
aFontList.AppendElement(kFontMeiryo);
aFontList.AppendElement(kFontArial);
aFontList.AppendElement(kFontLucidaSansUnicode);
aFontList.AppendElement(kFontEbrima);
break;
case 0x2d:
case 0x2e:
case 0x2f:
aFontList.AppendElement(kFontEbrima);
aFontList.AppendElement(kFontNyala);
aFontList.AppendElement(kFontSegoeUI);
aFontList.AppendElement(kFontSegoeUISymbol);
aFontList.AppendElement(kFontMeiryo);
break;
case 0x28: // Braille
aFontList.AppendElement(kFontSegoeUISymbol);
break;
case 0x30:
case 0x31:
aFontList.AppendElement(kFontMicrosoftYaHei);
break;
case 0x32:
aFontList.AppendElement(kFontMalgunGothic);
break;
case 0x4d:
aFontList.AppendElement(kFontSegoeUISymbol);
break;
case 0x9f:
aFontList.AppendElement(kFontMicrosoftYaHei);
aFontList.AppendElement(kFontYuGothic);
break;
case 0xa0: // Yi
case 0xa1:
case 0xa2:
case 0xa3:
case 0xa4:
aFontList.AppendElement(kFontMicrosoftYiBaiti);
aFontList.AppendElement(kFontSegoeUI);
break;
case 0xa5:
case 0xa6:
case 0xa7:
aFontList.AppendElement(kFontEbrima);
aFontList.AppendElement(kFontSegoeUI);
aFontList.AppendElement(kFontCambriaMath);
break;
case 0xa8:
aFontList.AppendElement(kFontMicrosoftPhagsPa);
aFontList.AppendElement(kFontNirmalaUI);
break;
case 0xa9:
aFontList.AppendElement(kFontMalgunGothic);
aFontList.AppendElement(kFontJavaneseText);
aFontList.AppendElement(kFontLeelawadeeUI);
break;
case 0xaa:
aFontList.AppendElement(kFontMyanmarText);
break;
case 0xab:
aFontList.AppendElement(kFontEbrima);
aFontList.AppendElement(kFontNyala);
break;
case 0xd7:
aFontList.AppendElement(kFontMalgunGothic);
break;
case 0xfb:
aFontList.AppendElement(kFontMicrosoftUighur);
aFontList.AppendElement(kFontGabriola);
aFontList.AppendElement(kFontSylfaen);
break;
case 0xfc:
case 0xfd:
aFontList.AppendElement(kFontTraditionalArabic);
aFontList.AppendElement(kFontArabicTypesetting);
break;
case 0xfe:
aFontList.AppendElement(kFontTraditionalArabic);
aFontList.AppendElement(kFontMicrosoftJhengHei);
break;
case 0xff:
aFontList.AppendElement(kFontMicrosoftJhengHei);
break;
default:
break;
}
}
// Arial Unicode MS has lots of glyphs for obscure characters,
// use it as a last resort
aFontList.AppendElement(kFontArialUnicodeMS);
// If we didn't begin with the color-emoji fonts, include them here
// so that they'll be preferred over user-installed (and possibly
// broken) fonts in the global fallback path.
if (!PrefersColor(aPresentation)) {
aFontList.AppendElement(kFontSegoeUIEmoji);
aFontList.AppendElement(kFontTwemojiMozilla);
}
}
bool gfxWindowsPlatform::DidRenderingDeviceReset(
DeviceResetReason* aResetReason) {
DeviceManagerDx* dm = DeviceManagerDx::Get();
if (!dm) {
return false;
}
return dm->HasDeviceReset(aResetReason);
}
void gfxWindowsPlatform::CompositorUpdated() {
DeviceManagerDx::Get()->ForceDeviceReset(
ForcedDeviceResetReason::COMPOSITOR_UPDATED);
UpdateRenderMode();
}
BOOL CALLBACK InvalidateWindowForDeviceReset(HWND aWnd, LPARAM aMsg) {
RedrawWindow(aWnd, nullptr, nullptr,
RDW_INVALIDATE | RDW_INTERNALPAINT | RDW_FRAME);
return TRUE;
}
void gfxWindowsPlatform::SchedulePaintIfDeviceReset() {
AUTO_PROFILER_LABEL("gfxWindowsPlatform::SchedulePaintIfDeviceReset", OTHER);
DeviceResetReason resetReason = DeviceResetReason::OK;
if (!DidRenderingDeviceReset(&resetReason)) {
return;
}
gfxCriticalNote << "(gfxWindowsPlatform) Detected device reset: "
<< (int)resetReason;
if (XRE_IsParentProcess()) {
// Trigger an ::OnPaint for each window.
::EnumThreadWindows(GetCurrentThreadId(), InvalidateWindowForDeviceReset,
0);
} else {
NS_DispatchToMainThread(NS_NewRunnableFunction(
"gfx::gfxWindowsPlatform::SchedulePaintIfDeviceReset", []() -> void {
gfxWindowsPlatform::GetPlatform()->CheckForContentOnlyDeviceReset();
}));
}
gfxCriticalNote << "(gfxWindowsPlatform) scheduled device update.";
}
void gfxWindowsPlatform::CheckForContentOnlyDeviceReset() {
if (!DidRenderingDeviceReset()) {
return;
}
bool isContentOnlyTDR;
D3D11DeviceStatus status;
DeviceManagerDx::Get()->ExportDeviceInfo(&status);
CompositorBridgeChild::Get()->SendCheckContentOnlyTDR(status.sequenceNumber(),
&isContentOnlyTDR);
// The parent process doesn't know about the reset yet, or the reset is
// local to our device.
if (isContentOnlyTDR) {
gfxCriticalNote << "A content-only TDR is detected.";
dom::ContentChild* cc = dom::ContentChild::GetSingleton();
cc->RecvReinitRenderingForDeviceReset();
}
}
nsTArray<uint8_t> gfxWindowsPlatform::GetPlatformCMSOutputProfileData() {
if (XRE_IsContentProcess()) {
auto& cmsOutputProfileData = GetCMSOutputProfileData();
// We should have set our profile data when we received our initial
// ContentDeviceData.
MOZ_ASSERT(cmsOutputProfileData.isSome(),
"Should have created output profile data when we received "
"initial content device data.");
// If we have data, it should not be empty.
MOZ_ASSERT_IF(cmsOutputProfileData.isSome(),
!cmsOutputProfileData->IsEmpty());
if (cmsOutputProfileData.isSome()) {
return cmsOutputProfileData.ref().Clone();
}
return nsTArray<uint8_t>();
}
return GetPlatformCMSOutputProfileData_Impl();
}
nsTArray<uint8_t> gfxWindowsPlatform::GetPlatformCMSOutputProfileData_Impl() {
static nsTArray<uint8_t> sCached = [&] {
// Check override pref first:
nsTArray<uint8_t> prefProfileData =
gfxPlatform::GetPrefCMSOutputProfileData();
if (!prefProfileData.IsEmpty()) {
return prefProfileData;
}
// -
// Otherwise, create a dummy DC and pull from that.
HDC dc = ::GetDC(nullptr);
if (!dc) {
return nsTArray<uint8_t>();
}
WCHAR profilePath[MAX_PATH];
DWORD profilePathLen = MAX_PATH;
bool getProfileResult = ::GetICMProfileW(dc, &profilePathLen, profilePath);
::ReleaseDC(nullptr, dc);
if (!getProfileResult) {
return nsTArray<uint8_t>();
}
void* mem = nullptr;
size_t size = 0;
qcms_data_from_unicode_path(profilePath, &mem, &size);
if (!mem) {
return nsTArray<uint8_t>();
}
nsTArray<uint8_t> result;
result.AppendElements(static_cast<uint8_t*>(mem), size);
free(mem);
return result;
}();
return sCached.Clone();
}
void gfxWindowsPlatform::GetDLLVersion(char16ptr_t aDLLPath,
nsAString& aVersion) {
DWORD versInfoSize, vers[4] = {0};
// version info not available case
aVersion.AssignLiteral(u"0.0.0.0");
versInfoSize = GetFileVersionInfoSizeW(aDLLPath, nullptr);
AutoTArray<BYTE, 512> versionInfo;
if (versInfoSize == 0) {
return;
}
// XXX(Bug 1631371) Check if this should use a fallible operation as it
// pretended earlier.
versionInfo.AppendElements(uint32_t(versInfoSize));
if (!GetFileVersionInfoW(aDLLPath, 0, versInfoSize,
LPBYTE(versionInfo.Elements()))) {
return;
}
UINT len = 0;
VS_FIXEDFILEINFO* fileInfo = nullptr;
if (!VerQueryValue(LPBYTE(versionInfo.Elements()), TEXT("\\"),
(LPVOID*)&fileInfo, &len) ||
len == 0 || fileInfo == nullptr) {
return;
}
DWORD fileVersMS = fileInfo->dwFileVersionMS;
DWORD fileVersLS = fileInfo->dwFileVersionLS;
vers[0] = HIWORD(fileVersMS);
vers[1] = LOWORD(fileVersMS);
vers[2] = HIWORD(fileVersLS);
vers[3] = LOWORD(fileVersLS);
char buf[256];
SprintfLiteral(buf, "%lu.%lu.%lu.%lu", vers[0], vers[1], vers[2], vers[3]);
aVersion.Assign(NS_ConvertUTF8toUTF16(buf));
}
static BOOL CALLBACK AppendClearTypeParams(HMONITOR aMonitor, HDC, LPRECT,
LPARAM aContext) {
MONITORINFOEXW monitorInfo;
monitorInfo.cbSize = sizeof(MONITORINFOEXW);
if (!GetMonitorInfoW(aMonitor, &monitorInfo)) {
return TRUE;
}
ClearTypeParameterInfo ctinfo;
ctinfo.displayName.Assign(monitorInfo.szDevice);
RefPtr<IDWriteRenderingParams> renderingParams;
HRESULT hr = Factory::GetDWriteFactory()->CreateMonitorRenderingParams(
aMonitor, getter_AddRefs(renderingParams));
if (FAILED(hr)) {
return TRUE;
}
ctinfo.gamma = renderingParams->GetGamma() * 1000;
ctinfo.pixelStructure = renderingParams->GetPixelGeometry();
ctinfo.clearTypeLevel = renderingParams->GetClearTypeLevel() * 100;
ctinfo.enhancedContrast = renderingParams->GetEnhancedContrast() * 100;
auto* params = reinterpret_cast<nsTArray<ClearTypeParameterInfo>*>(aContext);
params->AppendElement(ctinfo);
return TRUE;
}
void gfxWindowsPlatform::GetCleartypeParams(
nsTArray<ClearTypeParameterInfo>& aParams) {
aParams.Clear();
if (!DWriteEnabled()) {
return;
}
EnumDisplayMonitors(nullptr, nullptr, AppendClearTypeParams,
reinterpret_cast<LPARAM>(&aParams));
}
void gfxWindowsPlatform::FontsPrefsChanged(const char* aPref) {
bool clearTextFontCaches = true;
gfxPlatform::FontsPrefsChanged(aPref);
if (aPref &&
!strncmp(GFX_CLEARTYPE_PARAMS, aPref, strlen(GFX_CLEARTYPE_PARAMS))) {
gfxDWriteFont::UpdateClearTypeVars();
} else {
clearTextFontCaches = false;
}
if (clearTextFontCaches) {
gfxFontCache* fc = gfxFontCache::GetCache();
if (fc) {
fc->Flush();
}
}
}
bool gfxWindowsPlatform::IsOptimus() {
static int knowIsOptimus = -1;
if (knowIsOptimus == -1) {
// other potential optimus -- nvd3d9wrapx.dll & nvdxgiwrap.dll
if (GetModuleHandleA("nvumdshim.dll") ||
GetModuleHandleA("nvumdshimx.dll")) {
knowIsOptimus = 1;
} else {
knowIsOptimus = 0;
}
}
return knowIsOptimus;
}
static void InitializeANGLEConfig() {
FeatureState& d3d11ANGLE = gfxConfig::GetFeature(Feature::D3D11_HW_ANGLE);
if (!gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) {
d3d11ANGLE.DisableByDefault(FeatureStatus::Unavailable,
"D3D11 compositing is disabled",
"FEATURE_FAILURE_HW_ANGLE_D3D11_DISABLED"_ns);
return;
}
d3d11ANGLE.EnableByDefault();
nsCString message;
nsCString failureId;
if (!gfxPlatform::IsGfxInfoStatusOkay(nsIGfxInfo::FEATURE_DIRECT3D_11_ANGLE,
&message, failureId)) {
d3d11ANGLE.Disable(FeatureStatus::Blocklisted, message.get(), failureId);
}
}
void gfxWindowsPlatform::InitializeDirectDrawConfig() {
MOZ_ASSERT(XRE_IsParentProcess());
FeatureState& ddraw = gfxConfig::GetFeature(Feature::DIRECT_DRAW);
ddraw.EnableByDefault();
}
void gfxWindowsPlatform::InitializeConfig() {
if (XRE_IsParentProcess()) {
// The parent process first determines which features can be attempted.
// This information is relayed to content processes and the GPU process.
InitializeD3D11Config();
InitializeANGLEConfig();
InitializeD2DConfig();
} else {
ImportCachedContentDeviceData();
InitializeANGLEConfig();
}
}
void gfxWindowsPlatform::InitializeD3D11Config() {
MOZ_ASSERT(XRE_IsParentProcess());
FeatureState& d3d11 = gfxConfig::GetFeature(Feature::D3D11_COMPOSITING);
if (!gfxConfig::IsEnabled(Feature::HW_COMPOSITING)) {
d3d11.DisableByDefault(FeatureStatus::Unavailable,
"Hardware compositing is disabled",
"FEATURE_FAILURE_D3D11_NEED_HWCOMP"_ns);
return;
}
d3d11.EnableByDefault();
// Check if the user really, really wants WARP.
if (StaticPrefs::layers_d3d11_force_warp_AtStartup()) {
// Force D3D11 on even if we disabled it.
d3d11.UserForceEnable("User force-enabled WARP");
}
nsCString message;
nsCString failureId;
if (StaticPrefs::layers_d3d11_enable_blacklist_AtStartup() &&
!gfxPlatform::IsGfxInfoStatusOkay(nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS,
&message, failureId)) {
d3d11.Disable(FeatureStatus::Blocklisted, message.get(), failureId);
}
}
/* static */
void gfxWindowsPlatform::RecordContentDeviceFailure(
TelemetryDeviceCode aDevice) {
// If the parent process fails to acquire a device, we record this
// normally as part of the environment. The exceptional case we're
// looking for here is when the parent process successfully acquires
// a device, but the content process fails to acquire the same device.
// This would not normally be displayed in about:support.
if (!XRE_IsContentProcess()) {
return;
}
Telemetry::Accumulate(Telemetry::GFX_CONTENT_FAILED_TO_ACQUIRE_DEVICE,
uint32_t(aDevice));
}
void gfxWindowsPlatform::RecordStartupTelemetry() {
if (!XRE_IsParentProcess()) {
return;
}
DeviceManagerDx* dx = DeviceManagerDx::Get();
nsTArray<DXGI_OUTPUT_DESC1> outputs = dx->EnumerateOutputs();
uint32_t allSupportedColorSpaces = 0;
for (auto& output : outputs) {
uint32_t colorSpace = 1 << output.ColorSpace;
allSupportedColorSpaces |= colorSpace;
}
Telemetry::ScalarSet(
Telemetry::ScalarID::GFX_HDR_WINDOWS_DISPLAY_COLORSPACE_BITFIELD,
allSupportedColorSpaces);
}
// Supports lazy device initialization on Windows, so that WebRender can avoid
// initializing GPU state and allocating swap chains for most non-GPU processes.
void gfxWindowsPlatform::EnsureDevicesInitialized() {
MOZ_DIAGNOSTIC_ASSERT(!IsWin32kLockedDown());
if (!mInitializedDevices) {
mInitializedDevices = true;
InitializeDevices();
UpdateBackendPrefs();
}
}
bool gfxWindowsPlatform::DevicesInitialized() { return mInitializedDevices; }
void gfxWindowsPlatform::InitializeDevices() {
MOZ_ASSERT(NS_IsMainThread());
if (XRE_IsParentProcess()) {
// If we're the UI process, and the GPU process is enabled, then we don't
// initialize any DirectX devices. We do leave them enabled in gfxConfig
// though. If the GPU process fails to create these devices it will send
// a message back and we'll update their status.
if (gfxConfig::IsEnabled(Feature::GPU_PROCESS)) {
return;
}
// No GPU process, continue initializing devices as normal.
}
// If acceleration is disabled, we refuse to initialize anything.
if (!gfxConfig::IsEnabled(Feature::HW_COMPOSITING)) {
return;
}
// If we previously crashed initializing devices, bail out now.
D3D11LayersCrashGuard detectCrashes;
if (detectCrashes.Crashed()) {
gfxConfig::SetFailed(Feature::HW_COMPOSITING,
FeatureStatus::CrashedOnStartup,
"Crashed during startup in a previous session");
gfxConfig::SetFailed(
Feature::D3D11_COMPOSITING, FeatureStatus::CrashedOnStartup,
"Harware acceleration crashed during startup in a previous session");
gfxConfig::SetFailed(
Feature::DIRECT2D, FeatureStatus::CrashedOnStartup,
"Harware acceleration crashed during startup in a previous session");
return;
}
bool shouldUseD2D = gfxConfig::IsEnabled(Feature::DIRECT2D);
// First, initialize D3D11. If this succeeds we attempt to use Direct2D.
InitializeD3D11();
InitializeD2D();
if (!gfxConfig::IsEnabled(Feature::DIRECT2D) && XRE_IsContentProcess() &&
shouldUseD2D) {
RecordContentDeviceFailure(TelemetryDeviceCode::D2D1);
}
}
void gfxWindowsPlatform::InitializeD3D11() {
// This function attempts to initialize our D3D11 devices, if the hardware
// is not blocklisted for D3D11 layers. This first attempt will try to create
// a hardware accelerated device. If this creation fails or the hardware is
// blocklisted, then this function will abort if WARP is disabled, causing us
// to fallback to Basic layers. If WARP is not disabled it will use a WARP
// device which should always be available on Windows 7 and higher.
if (!gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) {
return;
}
DeviceManagerDx* dm = DeviceManagerDx::Get();
if (XRE_IsParentProcess()) {
if (!dm->CreateCompositorDevices()) {
return;
}
}
dm->CreateContentDevices();
// Content process failed to create the d3d11 device while parent process
// succeed.
if (XRE_IsContentProcess() &&
!gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) {
gfxCriticalError()
<< "[D3D11] Failed to create the D3D11 device in content \
process.";
}
}
void gfxWindowsPlatform::InitializeD2DConfig() {
FeatureState& d2d1 = gfxConfig::GetFeature(Feature::DIRECT2D);
if (!gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) {
d2d1.DisableByDefault(FeatureStatus::Unavailable,
"Direct2D requires Direct3D 11 compositing",
"FEATURE_FAILURE_D2D_D3D11_COMP"_ns);
return;
}
d2d1.SetDefaultFromPref(StaticPrefs::GetPrefName_gfx_direct2d_disabled(),
false,
StaticPrefs::GetPrefDefault_gfx_direct2d_disabled());
nsCString message;
nsCString failureId;
if (!gfxPlatform::IsGfxInfoStatusOkay(nsIGfxInfo::FEATURE_DIRECT2D, &message,
failureId)) {
d2d1.Disable(FeatureStatus::Blocklisted, message.get(), failureId);
}
if (!d2d1.IsEnabled() &&
StaticPrefs::gfx_direct2d_force_enabled_AtStartup()) {
d2d1.UserForceEnable("Force-enabled via user-preference");
}
}
void gfxWindowsPlatform::InitializeD2D() {
ScopedGfxFeatureReporter d2d1_1("D2D1.1");
FeatureState& d2d1 = gfxConfig::GetFeature(Feature::DIRECT2D);
DeviceManagerDx* dm = DeviceManagerDx::Get();
// We don't know this value ahead of time, but the user can force-override
// it, so we use Disable instead of SetFailed.
if (dm->IsWARP()) {
d2d1.Disable(FeatureStatus::Blocked,
"Direct2D is not compatible with Direct3D11 WARP",
"FEATURE_FAILURE_D2D_WARP_BLOCK"_ns);
}
// If we pass all the initial checks, we can proceed to runtime decisions.
if (!d2d1.IsEnabled()) {
return;
}
if (!Factory::SupportsD2D1()) {
d2d1.SetFailed(FeatureStatus::Unavailable,
"Failed to acquire a Direct2D 1.1 factory",
"FEATURE_FAILURE_D2D_FACTORY"_ns);
return;
}
if (!dm->GetContentDevice()) {
d2d1.SetFailed(FeatureStatus::Failed,
"Failed to acquire a Direct3D 11 content device",
"FEATURE_FAILURE_D2D_DEVICE"_ns);
return;
}
if (!dm->TextureSharingWorks()) {
d2d1.SetFailed(FeatureStatus::Failed,
"Direct3D11 device does not support texture sharing",
"FEATURE_FAILURE_D2D_TXT_SHARING"_ns);
return;
}
// Using Direct2D depends on DWrite support.
if (!DWriteEnabled() && !InitDWriteSupport()) {
d2d1.SetFailed(FeatureStatus::Failed,
"Failed to initialize DirectWrite support",
"FEATURE_FAILURE_D2D_DWRITE"_ns);
return;
}
// Verify that Direct2D device creation succeeded.
RefPtr<ID3D11Device> contentDevice = dm->GetContentDevice();
if (!Factory::SetDirect3D11Device(contentDevice)) {
d2d1.SetFailed(FeatureStatus::Failed, "Failed to create a Direct2D device",
"FEATURE_FAILURE_D2D_CREATE_FAILED"_ns);
return;
}
MOZ_ASSERT(d2d1.IsEnabled());
d2d1_1.SetSuccessful();
}
void gfxWindowsPlatform::InitGPUProcessSupport() {
FeatureState& gpuProc = gfxConfig::GetFeature(Feature::GPU_PROCESS);
if (!gpuProc.IsEnabled()) {
return;
}
if (!gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) {
// Don't use the GPU process if not using D3D11, unless software
// compositor is allowed
if (StaticPrefs::layers_gpu_process_allow_software_AtStartup()) {
return;
}
gpuProc.Disable(FeatureStatus::Unavailable,
"Not using GPU Process since D3D11 is unavailable",
"FEATURE_FAILURE_NO_D3D11"_ns);
}
// If we're still enabled at this point, the user set the force-enabled pref.
}
class D3DVsyncSource final : public VsyncSource {
public:
D3DVsyncSource()
: mPrevVsync(TimeStamp::Now()),
mVsyncEnabled(false),
mWaitVBlankMonitor(NULL) {
mVsyncThread = new base::Thread("WindowsVsyncThread");
MOZ_RELEASE_ASSERT(mVsyncThread->Start(),
"GFX: Could not start Windows vsync thread");
SetVsyncRate();
}
void SetVsyncRate() {
DWM_TIMING_INFO vblankTime;
// Make sure to init the cbSize, otherwise GetCompositionTiming will fail
vblankTime.cbSize = sizeof(DWM_TIMING_INFO);
HRESULT hr = DwmGetCompositionTimingInfo(0, &vblankTime);
if (SUCCEEDED(hr)) {
UNSIGNED_RATIO refreshRate = vblankTime.rateRefresh;
// We get the rate in hertz / time, but we want the rate in ms.
float rate =
((float)refreshRate.uiDenominator / (float)refreshRate.uiNumerator) *
1000;
mVsyncRate = TimeDuration::FromMilliseconds(rate);
} else {
mVsyncRate = TimeDuration::FromMilliseconds(1000.0 / 60.0);
}
}
virtual void Shutdown() override {
MOZ_ASSERT(NS_IsMainThread());
DisableVsync();
mVsyncThread->Stop();
delete mVsyncThread;
}
virtual void EnableVsync() override {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mVsyncThread->IsRunning());
{ // scope lock
if (mVsyncEnabled) {
return;
}
mVsyncEnabled = true;
}
mVsyncThread->message_loop()->PostTask(NewRunnableMethod(
"D3DVsyncSource::VBlankLoop", this, &D3DVsyncSource::VBlankLoop));
}
virtual void DisableVsync() override {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mVsyncThread->IsRunning());
if (!mVsyncEnabled) {
return;
}
mVsyncEnabled = false;
}
virtual bool IsVsyncEnabled() override {
MOZ_ASSERT(NS_IsMainThread());
return mVsyncEnabled;
}
virtual TimeDuration GetVsyncRate() override { return mVsyncRate; }
void ScheduleSoftwareVsync(TimeStamp aVsyncTimestamp) {
MOZ_ASSERT(IsInVsyncThread());
NS_WARNING(
"DwmComposition dynamically disabled, falling back to software "
"timers");
TimeStamp nextVsync = aVsyncTimestamp + mVsyncRate;
TimeDuration delay = nextVsync - TimeStamp::Now();
if (delay.ToMilliseconds() < 0) {
delay = mozilla::TimeDuration::FromMilliseconds(0);
}
mVsyncThread->message_loop()->PostDelayedTask(
NewRunnableMethod("D3DVsyncSource::VBlankLoop", this,
&D3DVsyncSource::VBlankLoop),
delay.ToMilliseconds());
}
// Returns the timestamp for the just happened vsync
TimeStamp GetVBlankTime() {
TimeStamp vsync = TimeStamp::Now();
TimeStamp now = vsync;
DWM_TIMING_INFO vblankTime;
// Make sure to init the cbSize, otherwise
// GetCompositionTiming will fail
vblankTime.cbSize = sizeof(DWM_TIMING_INFO);
HRESULT hr = DwmGetCompositionTimingInfo(0, &vblankTime);
if (!SUCCEEDED(hr)) {
return vsync;
}
LARGE_INTEGER frequency;
QueryPerformanceFrequency(&frequency);
LARGE_INTEGER qpcNow;
QueryPerformanceCounter(&qpcNow);
const int microseconds = 1000000;
int64_t adjust = qpcNow.QuadPart - vblankTime.qpcVBlank;
int64_t usAdjust = (adjust * microseconds) / frequency.QuadPart;
vsync -= TimeDuration::FromMicroseconds((double)usAdjust);
// On Windows 10 and on, DWMGetCompositionTimingInfo, mostly
// reports the upcoming vsync time, which is in the future.
// It can also sometimes report a vblank time in the past.
// Since large parts of Gecko assume TimeStamps can't be in future,
// use the previous vsync.
// Windows 10 and Intel HD vsync timestamps are messy and
// all over the place once in a while. Most of the time,
// it reports the upcoming vsync. Sometimes, that upcoming
// vsync is in the past. Sometimes that upcoming vsync is before
// the previously seen vsync.
// In these error cases, normalize to Now();
if (vsync >= now) {
vsync = vsync - mVsyncRate;
}
// On Windows 7 and 8, DwmFlush wakes up AFTER qpcVBlankTime
// from DWMGetCompositionTimingInfo. We can return the adjusted vsync.
if (vsync >= now) {
vsync = now;
}
// Our vsync time is some time very far in the past, adjust to Now.
// 4 ms is arbitrary, so feel free to pick something else if this isn't
// working. See the comment above.
if ((now - vsync).ToMilliseconds() > 4.0) {
vsync = now;
}
return vsync;
}
void VBlankLoop() {
MOZ_ASSERT(IsInVsyncThread());
MOZ_ASSERT(sizeof(int64_t) == sizeof(QPC_TIME));
TimeStamp vsync = TimeStamp::Now();
mPrevVsync = TimeStamp();
TimeStamp flushTime = TimeStamp::Now();
TimeDuration longVBlank = mVsyncRate * 2;
for (;;) {
{ // scope lock
if (!mVsyncEnabled) return;
}
// Large parts of gecko assume that the refresh driver timestamp
// must be <= Now() and cannot be in the future.
MOZ_ASSERT(vsync <= TimeStamp::Now());
NotifyVsync(vsync, vsync + mVsyncRate);
HRESULT hr = E_FAIL;
if (!StaticPrefs::gfx_vsync_force_disable_waitforvblank()) {
UpdateVBlankOutput();
if (mWaitVBlankOutput) {
const TimeStamp vblank_begin_wait = TimeStamp::Now();
{
AUTO_PROFILER_THREAD_SLEEP;
hr = mWaitVBlankOutput->WaitForVBlank();
}
if (SUCCEEDED(hr)) {
// vblank might return instantly when running headless,
// monitor powering off, etc. Since we're on a dedicated
// thread, instant-return should not happen in the normal
// case, so catch any odd behavior with a time cutoff:
TimeDuration vblank_wait = TimeStamp::Now() - vblank_begin_wait;
if (vblank_wait.ToMilliseconds() < 1.0) {
hr = E_FAIL; // fall back on old behavior
}
}
}
}
if (!SUCCEEDED(hr)) {
hr = DwmFlush();
}
if (!SUCCEEDED(hr)) {
// DWMFlush isn't working, fallback to software vsync.
ScheduleSoftwareVsync(TimeStamp::Now());
return;
}
TimeStamp now = TimeStamp::Now();
TimeDuration flushDiff = now - flushTime;
flushTime = now;
if ((flushDiff > longVBlank) || mPrevVsync.IsNull()) {
// Our vblank took longer than 2 intervals, readjust our timestamps
vsync = GetVBlankTime();
mPrevVsync = vsync;
} else {
// Instead of giving the actual vsync time, a constant interval
// between vblanks instead of the noise generated via hardware
// is actually what we want. Most apps just care about the diff
// between vblanks to animate, so a clean constant interval is
// smoother.
vsync = mPrevVsync + mVsyncRate;
if (vsync > now) {
// DWMFlush woke up very early, so readjust our times again
vsync = GetVBlankTime();
}
if (vsync <= mPrevVsync) {
vsync = TimeStamp::Now();
}
if ((now - vsync).ToMilliseconds() > 2.0) {
// Account for time drift here where vsync never quite catches up to
// Now and we'd fall ever so slightly further behind Now().
vsync = GetVBlankTime();
}
mPrevVsync = vsync;
}
} // end for
}
virtual ~D3DVsyncSource() { MOZ_ASSERT(NS_IsMainThread()); }
private:
bool IsInVsyncThread() {
return mVsyncThread->thread_id() == PlatformThread::CurrentId();
}
void UpdateVBlankOutput() {
HMONITOR primary_monitor =
MonitorFromWindow(nullptr, MONITOR_DEFAULTTOPRIMARY);
if (primary_monitor == mWaitVBlankMonitor && mWaitVBlankOutput) {
return;
}
mWaitVBlankMonitor = primary_monitor;
RefPtr<IDXGIOutput> output = nullptr;
if (DeviceManagerDx* dx = DeviceManagerDx::Get()) {
if (dx->GetOutputFromMonitor(mWaitVBlankMonitor, &output)) {
mWaitVBlankOutput = output;
return;
}
}
// failed to convert a monitor to an output so keep trying
mWaitVBlankOutput = nullptr;
}
TimeStamp mPrevVsync;
base::Thread* mVsyncThread;
TimeDuration mVsyncRate;
Atomic<bool> mVsyncEnabled;
HMONITOR mWaitVBlankMonitor;
RefPtr<IDXGIOutput> mWaitVBlankOutput;
}; // D3DVsyncSource
already_AddRefed<mozilla::gfx::VsyncSource>
gfxWindowsPlatform::CreateGlobalHardwareVsyncSource() {
MOZ_RELEASE_ASSERT(NS_IsMainThread(), "GFX: Not in main thread.");
RefPtr<VsyncSource> d3dVsyncSource = new D3DVsyncSource();
return d3dVsyncSource.forget();
}
void gfxWindowsPlatform::ImportGPUDeviceData(
const mozilla::gfx::GPUDeviceData& aData) {
MOZ_ASSERT(XRE_IsParentProcess());
gfxPlatform::ImportGPUDeviceData(aData);
gfxConfig::ImportChange(Feature::D3D11_COMPOSITING, aData.d3d11Compositing());
DeviceManagerDx* dm = DeviceManagerDx::Get();
if (gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) {
dm->ImportDeviceInfo(aData.gpuDevice().ref());
} else {
// There should be no devices, so this just takes away the device status.
dm->ResetDevices();
// Make sure we disable D2D if content processes might use it.
FeatureState& d2d1 = gfxConfig::GetFeature(Feature::DIRECT2D);
if (d2d1.IsEnabled()) {
d2d1.SetFailed(FeatureStatus::Unavailable,
"Direct2D requires Direct3D 11 compositing",
"FEATURE_FAILURE_D2D_D3D11_COMP"_ns);
}
}
// CanUseHardwareVideoDecoding depends on d3d11 state, so update
// the cached value now.
UpdateCanUseHardwareVideoDecoding();
// For completeness (and messaging in about:support). Content recomputes this
// on its own, and we won't use ANGLE in the UI process if we're using a GPU
// process.
UpdateANGLEConfig();
}
void gfxWindowsPlatform::ImportContentDeviceData(
const mozilla::gfx::ContentDeviceData& aData) {
MOZ_ASSERT(XRE_IsContentProcess());
gfxPlatform::ImportContentDeviceData(aData);
const DevicePrefs& prefs = aData.prefs();
gfxConfig::Inherit(Feature::D3D11_COMPOSITING, prefs.d3d11Compositing());
gfxConfig::Inherit(Feature::DIRECT2D, prefs.useD2D1());
if (gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) {
DeviceManagerDx* dm = DeviceManagerDx::Get();
dm->ImportDeviceInfo(aData.d3d11());
}
}
void gfxWindowsPlatform::BuildContentDeviceData(ContentDeviceData* aOut) {
// Check for device resets before giving back new graphics information.
UpdateRenderMode();
gfxPlatform::BuildContentDeviceData(aOut);
const FeatureState& d3d11 = gfxConfig::GetFeature(Feature::D3D11_COMPOSITING);
aOut->prefs().d3d11Compositing() = d3d11.GetValue();
aOut->prefs().useD2D1() = gfxConfig::GetValue(Feature::DIRECT2D);
if (d3d11.IsEnabled()) {
DeviceManagerDx* dm = DeviceManagerDx::Get();
dm->ExportDeviceInfo(&aOut->d3d11());
}
aOut->cmsOutputProfileData() =
gfxPlatform::GetPlatform()->GetPlatformCMSOutputProfileData();
}
bool gfxWindowsPlatform::CheckVariationFontSupport() {
// Variation font support is only available on Fall Creators Update or later.
return IsWin10FallCreatorsUpdateOrLater();
}
void gfxWindowsPlatform::GetPlatformDisplayInfo(
mozilla::widget::InfoObject& aObj) {
HwStretchingSupport stretch;
DeviceManagerDx::Get()->CheckHardwareStretchingSupport(stretch);
nsPrintfCString stretchValue(
"both=%u window-only=%u full-screen-only=%u none=%u error=%u",
stretch.mBoth, stretch.mWindowOnly, stretch.mFullScreenOnly,
stretch.mNone, stretch.mError);
aObj.DefineProperty("HardwareStretching", stretchValue.get());
ScaledResolutionSet scaled;
GetScaledResolutions(scaled);
if (scaled.IsEmpty()) {
return;
}
aObj.DefineProperty("ScaledResolutionCount", scaled.Length());
for (size_t i = 0; i < scaled.Length(); ++i) {
auto& s = scaled[i];
nsPrintfCString name("ScaledResolution%zu", i);
nsPrintfCString value("source %dx%d, target %dx%d", s.first.width,
s.first.height, s.second.width, s.second.height);
aObj.DefineProperty(name.get(), value.get());
}
}