96263aa7c6
Currently our ANGLE vendor/renderer detection is based ANGLE's D3D backends. To detect on the GL backend it'd be helpful to reuse the normal GL detection after extracting the relevant GL backend strings that ANGLE puts in GL_RENDERER in its GLES frontend. This is a step in that direction. Bug: 1203705 Change-Id: I5367c49e8aaee2e138088316566f95900b9c4831 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/405689 Reviewed-by: Greg Daniel <egdaniel@google.com> Commit-Queue: Brian Salomon <bsalomon@google.com>
361 lines
14 KiB
C++
361 lines
14 KiB
C++
|
|
/*
|
|
* Copyright 2014 Google Inc.
|
|
*
|
|
* Use of this source code is governed by a BSD-style license that can be
|
|
* found in the LICENSE file.
|
|
*/
|
|
|
|
#include "include/gpu/GrDirectContext.h"
|
|
#include "src/gpu/GrDirectContextPriv.h"
|
|
#include "tools/gpu/GrContextFactory.h"
|
|
#ifdef SK_GL
|
|
#include "tools/gpu/gl/GLTestContext.h"
|
|
#endif
|
|
|
|
#if SK_ANGLE
|
|
#include "tools/gpu/gl/angle/GLTestContext_angle.h"
|
|
#endif
|
|
#include "tools/gpu/gl/command_buffer/GLTestContext_command_buffer.h"
|
|
#ifdef SK_VULKAN
|
|
#include "tools/gpu/vk/VkTestContext.h"
|
|
#endif
|
|
#ifdef SK_METAL
|
|
#include "tools/gpu/mtl/MtlTestContext.h"
|
|
#endif
|
|
#ifdef SK_DIRECT3D
|
|
#include "tools/gpu/d3d/D3DTestContext.h"
|
|
#endif
|
|
#ifdef SK_DAWN
|
|
#include "tools/gpu/dawn/DawnTestContext.h"
|
|
#endif
|
|
#include "src/gpu/GrCaps.h"
|
|
#include "src/gpu/gl/GrGLGpu.h"
|
|
#include "tools/gpu/mock/MockTestContext.h"
|
|
|
|
#if defined(SK_BUILD_FOR_WIN) && defined(SK_ENABLE_DISCRETE_GPU)
|
|
extern "C" {
|
|
// NVIDIA documents that the presence and value of this symbol programmatically enable the high
|
|
// performance GPU in laptops with switchable graphics.
|
|
// https://docs.nvidia.com/gameworks/content/technologies/desktop/optimus.htm
|
|
// From testing, including this symbol, even if it is set to 0, we still get the NVIDIA GPU.
|
|
_declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001;
|
|
|
|
// AMD has a similar mechanism, although I don't have an AMD laptop, so this is untested.
|
|
// https://community.amd.com/thread/169965
|
|
__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
|
|
}
|
|
#endif
|
|
|
|
namespace sk_gpu_test {
|
|
GrContextFactory::GrContextFactory() { }
|
|
|
|
GrContextFactory::GrContextFactory(const GrContextOptions& opts)
|
|
: fGlobalOptions(opts) {
|
|
}
|
|
|
|
GrContextFactory::~GrContextFactory() {
|
|
this->destroyContexts();
|
|
}
|
|
|
|
void GrContextFactory::destroyContexts() {
|
|
// We must delete the test contexts in reverse order so that any child context is finished and
|
|
// deleted before a parent context. This relies on the fact that when we make a new context we
|
|
// append it to the end of fContexts array.
|
|
// TODO: Look into keeping a dependency dag for contexts and deletion order
|
|
for (int i = fContexts.count() - 1; i >= 0; --i) {
|
|
Context& context = fContexts[i];
|
|
SkScopeExit restore(nullptr);
|
|
if (context.fTestContext) {
|
|
restore = context.fTestContext->makeCurrentAndAutoRestore();
|
|
}
|
|
if (!context.fGrContext->unique()) {
|
|
context.fGrContext->releaseResourcesAndAbandonContext();
|
|
context.fAbandoned = true;
|
|
}
|
|
context.fGrContext->unref();
|
|
delete context.fTestContext;
|
|
}
|
|
fContexts.reset();
|
|
}
|
|
|
|
void GrContextFactory::abandonContexts() {
|
|
// We must abandon the test contexts in reverse order so that any child context is finished and
|
|
// abandoned before a parent context. This relies on the fact that when we make a new context we
|
|
// append it to the end of fContexts array.
|
|
// TODO: Look into keeping a dependency dag for contexts and deletion order
|
|
for (int i = fContexts.count() - 1; i >= 0; --i) {
|
|
Context& context = fContexts[i];
|
|
if (!context.fAbandoned) {
|
|
if (context.fTestContext) {
|
|
auto restore = context.fTestContext->makeCurrentAndAutoRestore();
|
|
context.fTestContext->testAbandon();
|
|
}
|
|
GrBackendApi api = context.fGrContext->backend();
|
|
bool requiresEarlyAbandon = api == GrBackendApi::kVulkan || api == GrBackendApi::kDawn;
|
|
if (requiresEarlyAbandon) {
|
|
context.fGrContext->abandonContext();
|
|
}
|
|
if (context.fTestContext) {
|
|
delete(context.fTestContext);
|
|
context.fTestContext = nullptr;
|
|
}
|
|
if (!requiresEarlyAbandon) {
|
|
context.fGrContext->abandonContext();
|
|
}
|
|
context.fAbandoned = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
void GrContextFactory::releaseResourcesAndAbandonContexts() {
|
|
// We must abandon the test contexts in reverse order so that any child context is finished and
|
|
// abandoned before a parent context. This relies on the fact that when we make a new context we
|
|
// append it to the end of fContexts array.
|
|
// TODO: Look into keeping a dependency dag for contexts and deletion order
|
|
for (int i = fContexts.count() - 1; i >= 0; --i) {
|
|
Context& context = fContexts[i];
|
|
SkScopeExit restore(nullptr);
|
|
if (!context.fAbandoned) {
|
|
if (context.fTestContext) {
|
|
restore = context.fTestContext->makeCurrentAndAutoRestore();
|
|
}
|
|
context.fGrContext->releaseResourcesAndAbandonContext();
|
|
if (context.fTestContext) {
|
|
delete context.fTestContext;
|
|
context.fTestContext = nullptr;
|
|
}
|
|
context.fAbandoned = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
GrDirectContext* GrContextFactory::get(ContextType type, ContextOverrides overrides) {
|
|
return this->getContextInfo(type, overrides).directContext();
|
|
}
|
|
|
|
ContextInfo GrContextFactory::getContextInfoInternal(ContextType type, ContextOverrides overrides,
|
|
GrDirectContext* shareContext,
|
|
uint32_t shareIndex) {
|
|
// (shareIndex != 0) -> (shareContext != nullptr)
|
|
SkASSERT((shareIndex == 0) || (shareContext != nullptr));
|
|
|
|
for (int i = 0; i < fContexts.count(); ++i) {
|
|
Context& context = fContexts[i];
|
|
if (context.fType == type &&
|
|
context.fOverrides == overrides &&
|
|
context.fShareContext == shareContext &&
|
|
context.fShareIndex == shareIndex &&
|
|
!context.fAbandoned) {
|
|
context.fTestContext->makeCurrent();
|
|
return ContextInfo(context.fType, context.fTestContext, context.fGrContext,
|
|
context.fOptions);
|
|
}
|
|
}
|
|
|
|
// If we're trying to create a context in a share group, find the primary context
|
|
Context* primaryContext = nullptr;
|
|
if (shareContext) {
|
|
for (int i = 0; i < fContexts.count(); ++i) {
|
|
if (!fContexts[i].fAbandoned && fContexts[i].fGrContext == shareContext) {
|
|
primaryContext = &fContexts[i];
|
|
break;
|
|
}
|
|
}
|
|
SkASSERT(primaryContext && primaryContext->fType == type);
|
|
}
|
|
|
|
std::unique_ptr<TestContext> testCtx;
|
|
GrBackendApi backend = ContextTypeBackend(type);
|
|
switch (backend) {
|
|
#ifdef SK_GL
|
|
case GrBackendApi::kOpenGL: {
|
|
GLTestContext* glShareContext = primaryContext
|
|
? static_cast<GLTestContext*>(primaryContext->fTestContext) : nullptr;
|
|
GLTestContext* glCtx;
|
|
switch (type) {
|
|
case kGL_ContextType:
|
|
glCtx = CreatePlatformGLTestContext(kGL_GrGLStandard, glShareContext);
|
|
break;
|
|
case kGLES_ContextType:
|
|
glCtx = CreatePlatformGLTestContext(kGLES_GrGLStandard, glShareContext);
|
|
break;
|
|
#if SK_ANGLE
|
|
case kANGLE_D3D9_ES2_ContextType:
|
|
glCtx = MakeANGLETestContext(ANGLEBackend::kD3D9, ANGLEContextVersion::kES2,
|
|
glShareContext).release();
|
|
// Chrome will only run on D3D9 with NVIDIA for 2012 and earlier drivers.
|
|
// (<= 269.73). We get shader link failures when testing on recent drivers
|
|
// using this backend.
|
|
if (glCtx) {
|
|
GrGLDriverInfo info = GrGLGetDriverInfo(glCtx->gl());
|
|
if (info.fANGLEVendor == GrGLVendor::kNVIDIA) {
|
|
delete glCtx;
|
|
return ContextInfo();
|
|
}
|
|
}
|
|
break;
|
|
case kANGLE_D3D11_ES2_ContextType:
|
|
glCtx = MakeANGLETestContext(ANGLEBackend::kD3D11, ANGLEContextVersion::kES2,
|
|
glShareContext).release();
|
|
break;
|
|
case kANGLE_D3D11_ES3_ContextType:
|
|
glCtx = MakeANGLETestContext(ANGLEBackend::kD3D11, ANGLEContextVersion::kES3,
|
|
glShareContext).release();
|
|
break;
|
|
case kANGLE_GL_ES2_ContextType:
|
|
glCtx = MakeANGLETestContext(ANGLEBackend::kOpenGL, ANGLEContextVersion::kES2,
|
|
glShareContext).release();
|
|
break;
|
|
case kANGLE_GL_ES3_ContextType:
|
|
glCtx = MakeANGLETestContext(ANGLEBackend::kOpenGL, ANGLEContextVersion::kES3,
|
|
glShareContext).release();
|
|
break;
|
|
#endif
|
|
#ifndef SK_NO_COMMAND_BUFFER
|
|
case kCommandBuffer_ContextType:
|
|
glCtx = CommandBufferGLTestContext::Create(glShareContext);
|
|
break;
|
|
#endif
|
|
default:
|
|
return ContextInfo();
|
|
}
|
|
if (!glCtx) {
|
|
return ContextInfo();
|
|
}
|
|
if (glCtx->gl()->fStandard == kGLES_GrGLStandard &&
|
|
(overrides & ContextOverrides::kFakeGLESVersionAs2)) {
|
|
glCtx->overrideVersion("OpenGL ES 2.0", "OpenGL ES GLSL ES 1.00");
|
|
}
|
|
testCtx.reset(glCtx);
|
|
break;
|
|
}
|
|
#endif // SK_GL
|
|
#ifdef SK_VULKAN
|
|
case GrBackendApi::kVulkan: {
|
|
VkTestContext* vkSharedContext = primaryContext
|
|
? static_cast<VkTestContext*>(primaryContext->fTestContext) : nullptr;
|
|
SkASSERT(kVulkan_ContextType == type);
|
|
testCtx.reset(CreatePlatformVkTestContext(vkSharedContext));
|
|
if (!testCtx) {
|
|
return ContextInfo();
|
|
}
|
|
|
|
// We previously had an issue where the VkDevice destruction would occasionally hang
|
|
// on systems with NVIDIA GPUs and having an existing GL context fixed it. Now (March
|
|
// 2020) we still need the GL context to keep Vulkan/TSAN bots from running incredibly
|
|
// slow. Perhaps this prevents repeated driver loading/unloading? Note that keeping
|
|
// a persistent VkTestContext around instead was tried and did not work.
|
|
if (!fSentinelGLContext) {
|
|
fSentinelGLContext.reset(CreatePlatformGLTestContext(kGL_GrGLStandard));
|
|
if (!fSentinelGLContext) {
|
|
fSentinelGLContext.reset(CreatePlatformGLTestContext(kGLES_GrGLStandard));
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
#endif
|
|
#ifdef SK_METAL
|
|
case GrBackendApi::kMetal: {
|
|
MtlTestContext* mtlSharedContext = primaryContext
|
|
? static_cast<MtlTestContext*>(primaryContext->fTestContext) : nullptr;
|
|
SkASSERT(kMetal_ContextType == type);
|
|
testCtx.reset(CreatePlatformMtlTestContext(mtlSharedContext));
|
|
if (!testCtx) {
|
|
return ContextInfo();
|
|
}
|
|
break;
|
|
}
|
|
#endif
|
|
#ifdef SK_DIRECT3D
|
|
case GrBackendApi::kDirect3D: {
|
|
D3DTestContext* d3dSharedContext = primaryContext
|
|
? static_cast<D3DTestContext*>(primaryContext->fTestContext) : nullptr;
|
|
SkASSERT(kDirect3D_ContextType == type);
|
|
testCtx.reset(CreatePlatformD3DTestContext(d3dSharedContext));
|
|
if (!testCtx) {
|
|
return ContextInfo();
|
|
}
|
|
break;
|
|
}
|
|
#endif
|
|
#ifdef SK_DAWN
|
|
case GrBackendApi::kDawn: {
|
|
DawnTestContext* dawnSharedContext = primaryContext
|
|
? static_cast<DawnTestContext*>(primaryContext->fTestContext) : nullptr;
|
|
testCtx.reset(CreatePlatformDawnTestContext(dawnSharedContext));
|
|
if (!testCtx) {
|
|
return ContextInfo();
|
|
}
|
|
break;
|
|
}
|
|
#endif
|
|
case GrBackendApi::kMock: {
|
|
TestContext* sharedContext = primaryContext ? primaryContext->fTestContext : nullptr;
|
|
SkASSERT(kMock_ContextType == type);
|
|
testCtx.reset(CreateMockTestContext(sharedContext));
|
|
if (!testCtx) {
|
|
return ContextInfo();
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
return ContextInfo();
|
|
}
|
|
|
|
SkASSERT(testCtx && testCtx->backend() == backend);
|
|
GrContextOptions grOptions = fGlobalOptions;
|
|
if (ContextOverrides::kAvoidStencilBuffers & overrides) {
|
|
grOptions.fAvoidStencilBuffers = true;
|
|
}
|
|
if (ContextOverrides::kReducedShaders & overrides) {
|
|
grOptions.fReducedShaderVariations = true;
|
|
}
|
|
sk_sp<GrDirectContext> grCtx;
|
|
{
|
|
auto restore = testCtx->makeCurrentAndAutoRestore();
|
|
grCtx = testCtx->makeContext(grOptions);
|
|
}
|
|
if (!grCtx) {
|
|
return ContextInfo();
|
|
}
|
|
|
|
if (shareContext) {
|
|
SkASSERT(grCtx->directContextID() != shareContext->directContextID());
|
|
}
|
|
|
|
// We must always add new contexts by pushing to the back so that when we delete them we delete
|
|
// them in reverse order in which they were made.
|
|
Context& context = fContexts.push_back();
|
|
context.fBackend = backend;
|
|
context.fTestContext = testCtx.release();
|
|
context.fGrContext = SkRef(grCtx.get());
|
|
context.fType = type;
|
|
context.fOverrides = overrides;
|
|
context.fAbandoned = false;
|
|
context.fShareContext = shareContext;
|
|
context.fShareIndex = shareIndex;
|
|
context.fOptions = grOptions;
|
|
context.fTestContext->makeCurrent();
|
|
return ContextInfo(context.fType, context.fTestContext, context.fGrContext, context.fOptions);
|
|
}
|
|
|
|
ContextInfo GrContextFactory::getContextInfo(ContextType type, ContextOverrides overrides) {
|
|
return this->getContextInfoInternal(type, overrides, nullptr, 0);
|
|
}
|
|
|
|
ContextInfo GrContextFactory::getSharedContextInfo(GrDirectContext* shareContext,
|
|
uint32_t shareIndex) {
|
|
SkASSERT(shareContext);
|
|
for (int i = 0; i < fContexts.count(); ++i) {
|
|
if (!fContexts[i].fAbandoned && fContexts[i].fGrContext == shareContext) {
|
|
return this->getContextInfoInternal(fContexts[i].fType, fContexts[i].fOverrides,
|
|
shareContext, shareIndex);
|
|
}
|
|
}
|
|
|
|
return ContextInfo();
|
|
}
|
|
|
|
} // namespace sk_gpu_test
|