/*------------------------------------------------------------------------- * drawElements Quality Program OpenGL ES Utilities * ------------------------------------------------ * * Copyright 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * *//*! * \file * \brief OpenGL ES 3plus wrapper context. *//*--------------------------------------------------------------------*/ #include "gluES3PlusWrapperContext.hpp" #include "gluRenderContext.hpp" #include "gluRenderConfig.hpp" #include "glwInitFunctions.hpp" #include "glwFunctionLoader.hpp" #include "gluContextFactory.hpp" #include "deThreadLocal.hpp" #include "glwEnums.hpp" #include #include #include #include #include #include namespace glu { namespace es3plus { using std::vector; using std::string; class Context { public: Context (const glw::Functions& gl_); ~Context (void); void addExtension (const char* name); const glw::Functions& gl; //!< GL 4.3 core context functions. // Wrapper state. string vendor; string version; string renderer; string shadingLanguageVersion; string extensions; vector extensionList; bool primitiveRestartEnabled; deUint32 defaultVAO; bool defaultVAOBound; }; Context::Context (const glw::Functions& gl_) : gl (gl_) , vendor ("drawElements") , version ("OpenGL ES 3.1") , renderer ((const char*)gl.getString(GL_RENDERER)) , shadingLanguageVersion ("OpenGL ES GLSL ES 3.1") , primitiveRestartEnabled (false) , defaultVAO (0) , defaultVAOBound (false) { gl.genVertexArrays(1, &defaultVAO); if (gl.getError() != GL_NO_ERROR || defaultVAO == 0) throw tcu::InternalError("Failed to allocate VAO for emulation"); gl.bindVertexArray(defaultVAO); if (gl.getError() != GL_NO_ERROR) throw tcu::InternalError("Failed to bind default VAO"); defaultVAOBound = true; gl.enable(GL_PROGRAM_POINT_SIZE); gl.getError(); // supress potential errors, feature is not critical gl.enable(GL_TEXTURE_CUBE_MAP_SEAMLESS); gl.getError(); // suppress // Extensions addExtension("GL_OES_texture_stencil8"); addExtension("GL_OES_sample_shading"); addExtension("GL_OES_sample_variables"); addExtension("GL_OES_shader_multisample_interpolation"); addExtension("GL_OES_shader_image_atomic"); addExtension("GL_OES_texture_storage_multisample_2d_array"); // \todo [2014-03-18 pyry] Enable only if base ctx supports these or compatible GL_NV_blend_equation_advanced ext addExtension("GL_KHR_blend_equation_advanced"); addExtension("GL_KHR_blend_equation_advanced_coherent"); addExtension("GL_EXT_shader_io_blocks"); addExtension("GL_EXT_geometry_shader"); addExtension("GL_EXT_geometry_point_size"); addExtension("GL_EXT_tessellation_shader"); addExtension("GL_EXT_tessellation_point_size"); addExtension("GL_EXT_gpu_shader5"); addExtension("GL_KHR_debug"); addExtension("GL_EXT_texture_cube_map_array"); } Context::~Context (void) { if (defaultVAO) gl.deleteVertexArrays(1, &defaultVAO); } void Context::addExtension (const char* name) { if (!extensions.empty()) extensions += " "; extensions += name; extensionList.push_back(name); } static de::ThreadLocal tls_context; void setCurrentContext (Context* context) { tls_context.set(context); } inline Context* getCurrentContext (void) { return (Context*)tls_context.get(); } static GLW_APICALL void GLW_APIENTRY getIntegerv (deUint32 pname, deInt32* params) { Context* context = getCurrentContext(); if (context) { if (pname == GL_NUM_EXTENSIONS && params) *params = (deInt32)context->extensionList.size(); else context->gl.getIntegerv(pname, params); } } static GLW_APICALL const glw::GLubyte* GLW_APIENTRY getString (deUint32 name) { Context* context = getCurrentContext(); if (context) { switch (name) { case GL_VENDOR: return (const glw::GLubyte*)context->vendor.c_str(); case GL_VERSION: return (const glw::GLubyte*)context->version.c_str(); case GL_RENDERER: return (const glw::GLubyte*)context->renderer.c_str(); case GL_SHADING_LANGUAGE_VERSION: return (const glw::GLubyte*)context->shadingLanguageVersion.c_str(); case GL_EXTENSIONS: return (const glw::GLubyte*)context->extensions.c_str(); default: return context->gl.getString(name); } } else return DE_NULL; } static GLW_APICALL const glw::GLubyte* GLW_APIENTRY getStringi (deUint32 name, deUint32 index) { Context* context = getCurrentContext(); if (context) { if (name == GL_EXTENSIONS) { if ((size_t)index < context->extensionList.size()) return (const glw::GLubyte*)context->extensionList[index].c_str(); else return context->gl.getStringi(name, ~0u); } else return context->gl.getStringi(name, index); } else return DE_NULL; } static GLW_APICALL void GLW_APIENTRY enable (deUint32 cap) { Context* context = getCurrentContext(); if (context) { if (cap == GL_PRIMITIVE_RESTART_FIXED_INDEX) { context->primitiveRestartEnabled = true; // \todo [2013-09-30 pyry] Call to glPrimitiveRestartIndex() is required prior to all draw calls! } else context->gl.enable(cap); } } static GLW_APICALL void GLW_APIENTRY disable (deUint32 cap) { Context* context = getCurrentContext(); if (context) { if (cap == GL_PRIMITIVE_RESTART_FIXED_INDEX) context->primitiveRestartEnabled = false; else context->gl.disable(cap); } } static GLW_APICALL void GLW_APIENTRY bindVertexArray (deUint32 array) { Context* context = getCurrentContext(); if (context) { context->gl.bindVertexArray(array == 0 ? context->defaultVAO : array); context->defaultVAOBound = (array == 0); } } static GLW_APICALL void GLW_APIENTRY hint (deUint32 target, deUint32 mode) { Context* context = getCurrentContext(); if (context) { if (target != GL_GENERATE_MIPMAP_HINT) context->gl.hint(target, mode); // \todo [2013-09-30 pyry] Verify mode. } } static void translateShaderSource (deUint32 shaderType, std::ostream& dst, const std::string& src, const std::vector& filteredExtensions) { bool foundVersion = false; std::istringstream istr (src); std::string line; int srcLineNdx = 1; while (std::getline(istr, line, '\n')) { if (line == "#version 310 es") { foundVersion = true; dst << "#version 430\n"; if (shaderType == GL_VERTEX_SHADER) { // ARB_separate_shader_objects requires gl_PerVertex to be explicitly declared dst << "out gl_PerVertex {\n" << " vec4 gl_Position;\n" << " float gl_PointSize;\n" << " float gl_ClipDistance[];\n" << "};\n" << "#line " << (srcLineNdx + 1) << "\n"; } } else if (line == "#version 300 es") { foundVersion = true; dst << "#version 330\n"; } else if (line.substr(0, 10) == "precision ") { const size_t precPos = 10; const size_t precEndPos = line.find(' ', precPos); const size_t endPos = line.find(';'); if (precEndPos != std::string::npos && endPos != std::string::npos && endPos > precEndPos+1) { const size_t typePos = precEndPos+1; const std::string precision = line.substr(precPos, precEndPos-precPos); const std::string type = line.substr(typePos, endPos-typePos); const bool precOk = precision == "lowp" || precision == "mediump" || precision == "highp"; if (precOk && (type == "image2D" || type == "uimage2D" || type == "iimage2D" || type == "imageCube" || type == "uimageCube" || type == "iimageCube" || type == "image3D" || type == "iimage3D" || type == "uimage3D" || type == "image2DArray" || type == "iimage2DArray" || type == "uimage2DArray" || type == "imageCubeArray" || type == "iimageCubeArray" || type == "uimageCubeArray")) dst << "// "; // Filter out statement } dst << line << "\n"; } else if (line.substr(0, 11) == "#extension ") { const size_t extNamePos = 11; const size_t extNameEndPos = line.find_first_of(" :", extNamePos); const size_t behaviorPos = line.find_first_not_of(" :", extNameEndPos); if (extNameEndPos != std::string::npos && behaviorPos != std::string::npos) { const std::string extName = line.substr(extNamePos, extNameEndPos-extNamePos); const std::string behavior = line.substr(behaviorPos); const bool filteredExtension = std::find(filteredExtensions.begin(), filteredExtensions.end(), extName) != filteredExtensions.end(); const bool validBehavior = behavior == "require" || behavior == "enable" || behavior == "warn" || behavior == "disable"; if (filteredExtension && validBehavior) dst << "// "; // Filter out extension } dst << line << "\n"; } else if (line.substr(0, 21) == "layout(blend_support_") dst << "// " << line << "\n"; else dst << line << "\n"; srcLineNdx += 1; } DE_ASSERT(foundVersion); DE_UNREF(foundVersion); } static std::string translateShaderSources (deUint32 shaderType, deInt32 count, const char* const* strings, const int* length, const std::vector& filteredExtensions) { std::ostringstream srcIn; std::ostringstream srcOut; for (int ndx = 0; ndx < count; ndx++) { const int len = length && length[ndx] >= 0 ? length[ndx] : (int)strlen(strings[ndx]); srcIn << std::string(strings[ndx], strings[ndx] + len); } translateShaderSource(shaderType, srcOut, srcIn.str(), filteredExtensions); return srcOut.str(); } static GLW_APICALL void GLW_APIENTRY shaderSource (deUint32 shader, deInt32 count, const char* const* strings, const int* length) { Context* context = getCurrentContext(); if (context) { if (count > 0 && strings) { deInt32 shaderType = GL_NONE; context->gl.getShaderiv(shader, GL_SHADER_TYPE, &shaderType); { const std::string translatedSrc = translateShaderSources(shaderType, count, strings, length, context->extensionList); const char* srcPtr = translatedSrc.c_str(); context->gl.shaderSource(shader, 1, &srcPtr, DE_NULL); } } else context->gl.shaderSource(shader, count, strings, length); } } static GLW_APICALL void GLW_APIENTRY bindFramebuffer (deUint32 target, deUint32 framebuffer) { Context* context = getCurrentContext(); if (context) { context->gl.bindFramebuffer(target, framebuffer); // Emulate ES behavior where sRGB conversion is only controlled by color buffer format. if (target == GL_FRAMEBUFFER || target == GL_DRAW_FRAMEBUFFER || target == GL_READ_FRAMEBUFFER) ((framebuffer != 0) ? context->gl.enable : context->gl.disable)(GL_FRAMEBUFFER_SRGB); } } static GLW_APICALL void GLW_APIENTRY blendBarrierKHR (void) { Context* context = getCurrentContext(); if (context) { // \todo [2014-03-18 pyry] Use BlendBarrierNV() if supported context->gl.finish(); } } static GLW_APICALL deUint32 GLW_APIENTRY createShaderProgramv (deUint32 type, deInt32 count, const char* const* strings) { Context* context = getCurrentContext(); if (context) { if (count > 0 && strings) { const std::string translatedSrc = translateShaderSources(type, count, strings, DE_NULL, context->extensionList); const char* srcPtr = translatedSrc.c_str(); return context->gl.createShaderProgramv(type, 1, &srcPtr); } else return context->gl.createShaderProgramv(type, count, strings); } return 0; } static void initFunctions (glw::Functions* dst, const glw::Functions& src) { // Functions directly passed to GL context. #include "gluES3PlusWrapperFuncs.inl" // Wrapped functions. dst->bindVertexArray = bindVertexArray; dst->disable = disable; dst->enable = enable; dst->getIntegerv = getIntegerv; dst->getString = getString; dst->getStringi = getStringi; dst->hint = hint; dst->shaderSource = shaderSource; dst->createShaderProgramv = createShaderProgramv; dst->bindFramebuffer = bindFramebuffer; // Extension functions { using std::map; class ExtFuncLoader : public glw::FunctionLoader { public: ExtFuncLoader (const map& extFuncs) : m_extFuncs(extFuncs) { } glw::GenericFuncType get (const char* name) const { map::const_iterator pos = m_extFuncs.find(name); return pos != m_extFuncs.end() ? pos->second : DE_NULL; } private: const map& m_extFuncs; }; map extFuncMap; const ExtFuncLoader extFuncLoader (extFuncMap); // OES_sample_shading extFuncMap["glMinSampleShadingOES"] = (glw::GenericFuncType)src.minSampleShading; // OES_texture_storage_multisample_2d_array extFuncMap["glTexStorage3DMultisampleOES"] = (glw::GenericFuncType)src.texStorage3DMultisample; // KHR_blend_equation_advanced extFuncMap["glBlendBarrierKHR"] = (glw::GenericFuncType)blendBarrierKHR; // EXT_tessellation_shader extFuncMap["glPatchParameteriEXT"] = (glw::GenericFuncType)src.patchParameteri; // EXT_geometry_shader extFuncMap["glFramebufferTextureEXT"] = (glw::GenericFuncType)src.framebufferTexture; // KHR_debug extFuncMap["glDebugMessageControlKHR"] = (glw::GenericFuncType)src.debugMessageControl; extFuncMap["glDebugMessageInsertKHR"] = (glw::GenericFuncType)src.debugMessageInsert; extFuncMap["glDebugMessageCallbackKHR"] = (glw::GenericFuncType)src.debugMessageCallback; extFuncMap["glGetDebugMessageLogKHR"] = (glw::GenericFuncType)src.getDebugMessageLog; extFuncMap["glGetPointervKHR"] = (glw::GenericFuncType)src.getPointerv; extFuncMap["glPushDebugGroupKHR"] = (glw::GenericFuncType)src.pushDebugGroup; extFuncMap["glPopDebugGroupKHR"] = (glw::GenericFuncType)src.popDebugGroup; extFuncMap["glObjectLabelKHR"] = (glw::GenericFuncType)src.objectLabel; extFuncMap["glGetObjectLabelKHR"] = (glw::GenericFuncType)src.getObjectLabel; extFuncMap["glObjectPtrLabelKHR"] = (glw::GenericFuncType)src.objectPtrLabel; extFuncMap["glGetObjectPtrLabelKHR"] = (glw::GenericFuncType)src.getObjectPtrLabel; { int numExts = 0; dst->getIntegerv(GL_NUM_EXTENSIONS, &numExts); if (numExts > 0) { vector extStr(numExts); for (int ndx = 0; ndx < numExts; ndx++) extStr[ndx] = (const char*)dst->getStringi(GL_EXTENSIONS, ndx); glw::initExtensionsES(dst, &extFuncLoader, (int)extStr.size(), &extStr[0]); } } } } } // es3plus ES3PlusWrapperContext::ES3PlusWrapperContext (const ContextFactory& factory, const RenderConfig& config, const tcu::CommandLine& cmdLine) : m_context (DE_NULL) , m_wrapperCtx (DE_NULL) { // Flags that are valid for both core & es context. Currently only excludes CONTEXT_FORWARD_COMPATIBLE const ContextFlags validContextFlags = CONTEXT_ROBUST | CONTEXT_DEBUG; static const ContextType wrappableNativeTypes[] = { ContextType(ApiType::core(4,4), config.type.getFlags() & validContextFlags), // !< higher in the list, preferred ContextType(ApiType::core(4,3), config.type.getFlags() & validContextFlags), }; if (config.type.getAPI() != ApiType::es(3,1)) throw tcu::NotSupportedError("Unsupported context type (ES3.1 wrapper supports only ES3.1)"); // try to create any wrappable context for (int nativeCtxNdx = 0; nativeCtxNdx < DE_LENGTH_OF_ARRAY(wrappableNativeTypes); ++nativeCtxNdx) { glu::ContextType nativeContext = wrappableNativeTypes[nativeCtxNdx]; try { glu::RenderConfig nativeConfig = config; nativeConfig.type = nativeContext; m_context = factory.createContext(nativeConfig, cmdLine); m_wrapperCtx = new es3plus::Context(m_context->getFunctions()); es3plus::setCurrentContext(m_wrapperCtx); es3plus::initFunctions(&m_functions, m_context->getFunctions()); break; } catch (...) { es3plus::setCurrentContext(DE_NULL); delete m_wrapperCtx; delete m_context; m_wrapperCtx = DE_NULL; m_context = DE_NULL; // throw only if all tries failed (that is, this was the last potential target) if (nativeCtxNdx + 1 == DE_LENGTH_OF_ARRAY(wrappableNativeTypes)) throw; else continue; } } } ES3PlusWrapperContext::~ES3PlusWrapperContext (void) { delete m_wrapperCtx; delete m_context; } ContextType ES3PlusWrapperContext::getType (void) const { return ContextType(ApiType::es(3,1), m_context->getType().getFlags()); } } // glu