/* * 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 "GrContextFactory.h" #include "GrContextPriv.h" #include "gl/GLTestContext.h" #if SK_ANGLE #include "gl/angle/GLTestContext_angle.h" #endif #include "gl/command_buffer/GLTestContext_command_buffer.h" #ifdef SK_VULKAN #include "vk/VkTestContext.h" #endif #ifdef SK_METAL #include "mtl/MtlTestContext.h" #endif #include "gl/null/NullGLTestContext.h" #include "gl/GrGLGpu.h" #include "mock/MockTestContext.h" #include "GrCaps.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(); delete(context.fTestContext); context.fTestContext = nullptr; } 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(); context.fAbandoned = true; if (context.fTestContext) { delete context.fTestContext; context.fTestContext = nullptr; } } } } GrContext* GrContextFactory::get(ContextType type, ContextOverrides overrides) { return this->getContextInfo(type, overrides).grContext(); } ContextInfo GrContextFactory::getContextInfoInternal(ContextType type, ContextOverrides overrides, GrContext* 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 master context Context* masterContext = nullptr; if (shareContext) { for (int i = 0; i < fContexts.count(); ++i) { if (!fContexts[i].fAbandoned && fContexts[i].fGrContext == shareContext) { masterContext = &fContexts[i]; break; } } SkASSERT(masterContext && masterContext->fType == type); } std::unique_ptr testCtx; GrBackendApi backend = ContextTypeBackend(type); switch (backend) { case GrBackendApi::kOpenGL: { GLTestContext* glShareContext = masterContext ? static_cast(masterContext->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(); 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 case kNullGL_ContextType: glCtx = CreateNullGLTestContext( ContextOverrides::kRequireNVPRSupport & overrides, glShareContext); break; default: return ContextInfo(); } if (!glCtx) { return ContextInfo(); } testCtx.reset(glCtx); break; } #ifdef SK_VULKAN case GrBackendApi::kVulkan: { VkTestContext* vkSharedContext = masterContext ? static_cast(masterContext->fTestContext) : nullptr; SkASSERT(kVulkan_ContextType == type); if (ContextOverrides::kRequireNVPRSupport & overrides) { return ContextInfo(); } testCtx.reset(CreatePlatformVkTestContext(vkSharedContext)); if (!testCtx) { return ContextInfo(); } // There is some bug (either in Skia or the NV Vulkan driver) where VkDevice // destruction will hang occaisonally. For some reason having an existing GL // context fixes this. if (!fSentinelGLContext) { fSentinelGLContext.reset(CreatePlatformGLTestContext(kGL_GrGLStandard)); if (!fSentinelGLContext) { fSentinelGLContext.reset(CreatePlatformGLTestContext(kGLES_GrGLStandard)); } } break; } #endif #ifdef SK_METAL case GrBackendApi::kMetal: { SkASSERT(!masterContext); testCtx.reset(CreatePlatformMtlTestContext(nullptr)); if (!testCtx) { return ContextInfo(); } break; } #endif case GrBackendApi::kMock: { TestContext* sharedContext = masterContext ? masterContext->fTestContext : nullptr; SkASSERT(kMock_ContextType == type); if (ContextOverrides::kRequireNVPRSupport & overrides) { return ContextInfo(); } testCtx.reset(CreateMockTestContext(sharedContext)); if (!testCtx) { return ContextInfo(); } break; } default: return ContextInfo(); } SkASSERT(testCtx && testCtx->backend() == backend); GrContextOptions grOptions = fGlobalOptions; if (ContextOverrides::kDisableNVPR & overrides) { grOptions.fSuppressPathRendering = true; } if (ContextOverrides::kAvoidStencilBuffers & overrides) { grOptions.fAvoidStencilBuffers = true; } sk_sp grCtx; { auto restore = testCtx->makeCurrentAndAutoRestore(); grCtx = testCtx->makeGrContext(grOptions); } if (!grCtx.get()) { return ContextInfo(); } if (ContextOverrides::kRequireNVPRSupport & overrides) { if (!grCtx->contextPriv().caps()->shaderCaps()->pathRenderingSupport()) { return ContextInfo(); } } // 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(GrContext* 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