/*-------------------------------------------------------------------------
* 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 <sstream>
#include <vector>
#include <string>
#include <cstring>
#include <algorithm>
#include <map>
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<string> 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<std::string>& 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<std::string>& 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<string, glw::GenericFuncType>& extFuncs)
: m_extFuncs(extFuncs)
{
}
glw::GenericFuncType get (const char* name) const
{
map<string, glw::GenericFuncType>::const_iterator pos = m_extFuncs.find(name);
return pos != m_extFuncs.end() ? pos->second : DE_NULL;
}
private:
const map<string, glw::GenericFuncType>& m_extFuncs;
};
map<string, glw::GenericFuncType> 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<const char*> 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