#include "DeviceGL.h" #include "GeometryGL.h" #include "ShaderGL.h" #include "TextureGL.h" #include "FramebufferGL.h" #include "ContextGL.h" #include "HeadersGL.h" #include "rbx/Profiler.h" #include #include LOGGROUP(Graphics) FASTFLAGVARIABLE(DebugGraphicsGL, false) FASTFLAGVARIABLE(GraphicsGL3, false) FASTFLAG(RenderVR) FASTFLAGVARIABLE(GraphicsGLReduceLatency, false) namespace RBX { namespace Graphics { static std::set getExtensions() { std::set result; if (const char* extensionString = reinterpret_cast(glGetString(GL_EXTENSIONS))) { std::istringstream ext(extensionString); std::string str; while (ext >> str) result.insert(str); return result; } #ifndef GLES if (FFlag::GraphicsGL3 && GLEW_VERSION_3_0) { int extensionCount = 0; glGetIntegerv(GL_NUM_EXTENSIONS, &extensionCount); for (int i = 0; i < extensionCount; ++i) result.insert(reinterpret_cast(glGetStringi(GL_EXTENSIONS, i))); } #endif return result; } #ifdef GLES static DeviceCapsGL createDeviceCaps(ContextGL* context, const std::set& extensions) { const char* renderer = reinterpret_cast(glGetString(GL_RENDERER)); const char* version = reinterpret_cast(glGetString(GL_VERSION)); bool version3 = FFlag::GraphicsGL3 && strncmp(version, "OpenGL ES ", 10) == 0 && version[10] >= '3'; DeviceCapsGL caps; GLint texSize; glGetIntegerv(GL_MAX_TEXTURE_SIZE, &texSize); GLint stencilBits; glGetIntegerv(GL_STENCIL_BITS, &stencilBits); caps.supportsFramebuffer = true; caps.supportsShaders = true; caps.supportsFFP = false; caps.supportsStencil = stencilBits >= 8; caps.supportsIndex32 = version3 || extensions.count("GL_OES_element_index_uint"); caps.supportsTextureDXT = false; caps.supportsTexturePVR = extensions.count("GL_IMG_texture_compression_pvrtc"); caps.supportsTextureHalfFloat = version3 || extensions.count("GL_OES_texture_half_float"); caps.supportsTexture3D = version3; caps.supportsTextureNPOT = version3; caps.supportsTextureETC1 = extensions.count("GL_OES_compressed_ETC1_RGB8_texture"); caps.supportsTexturePartialMipChain = version3 || extensions.count("GL_APPLE_texture_max_level") || (!FFlag::GraphicsGL3 && strstr(version, "OpenGL ES 3")); caps.maxDrawBuffers = 1; if (version3) { GLint samples = 1; glGetIntegerv(GL_MAX_SAMPLES, &samples); caps.maxSamples = samples; } else { caps.maxSamples = 1; } caps.maxTextureSize = texSize; caps.maxTextureUnits = version3 ? 16 : 8; caps.colorOrderBGR = false; caps.needsHalfPixelOffset = false; caps.requiresRenderTargetFlipping = true; caps.retina = context->isMainFramebufferRetina(); caps.ext3 = version3; caps.extVertexArrayObject = version3 || (extensions.count("GL_OES_vertex_array_object") && !strstr(renderer, "Adreno")); caps.extTextureStorage = version3 || extensions.count("GL_EXT_texture_storage"); caps.extMapBuffer = version3 || extensions.count("GL_OES_mapbuffer"); caps.extMapBufferRange = version3 || extensions.count("GL_EXT_map_buffer_range"); caps.extTimerQuery = false; caps.extDebugMarkers = extensions.count("GL_EXT_debug_marker"); caps.extSync = version3; return caps; } #else static DeviceCapsGL createDeviceCapsOld(ContextGL* context) { DeviceCapsGL caps; GLint texSize = 0; glGetIntegerv(GL_MAX_TEXTURE_SIZE, &texSize); GLint stencilBits = 0; glGetIntegerv(GL_STENCIL_BITS, &stencilBits); caps.supportsFramebuffer = GLEW_VERSION_3_0 || GLEW_ARB_framebuffer_object || GLEW_EXT_framebuffer_object; caps.supportsShaders = GLEW_VERSION_2_0 || (GLEW_ARB_shading_language_100 && GLEW_ARB_shader_objects && GLEW_ARB_fragment_shader && GLEW_ARB_vertex_shader); caps.supportsFFP = false; caps.supportsStencil = GLEW_VERSION_2_0 && stencilBits >= 8; // we need full two-sided stencil support caps.supportsIndex32 = true; caps.supportsTextureDXT = (GLEW_VERSION_1_3 || GLEW_ARB_texture_compression) && GLEW_EXT_texture_compression_s3tc; caps.supportsTexturePVR = false; caps.supportsTextureHalfFloat = !!GLEW_ARB_half_float_pixel; caps.supportsTexture3D = GLEW_VERSION_1_2 || GLEW_EXT_texture3D; caps.supportsTextureETC1 = false; caps.supportsTexturePartialMipChain = true; // According to http://aras-p.info/blog/2012/10/17/non-power-of-two-textures/, GL extensions lie caps.supportsTextureNPOT = GLEW_ARB_texture_non_power_of_two && texSize >= 8192; if (caps.supportsFramebuffer && (GLEW_VERSION_3_0 || GLEW_ARB_draw_buffers || GLEW_ATI_draw_buffers)) { GLint drawBuffers = 0; glGetIntegerv(GL_MAX_DRAW_BUFFERS, &drawBuffers); caps.maxDrawBuffers = drawBuffers; } else { caps.maxDrawBuffers = 1; } if (caps.supportsFramebuffer && (GLEW_VERSION_3_3 || GLEW_EXT_framebuffer_multisample)) { GLint samples = 0; glGetIntegerv(GL_MAX_SAMPLES, &samples); caps.maxSamples = samples; } else { caps.maxSamples = 1; } caps.maxTextureSize = texSize; caps.maxTextureUnits = 16; caps.colorOrderBGR = false; caps.needsHalfPixelOffset = false; caps.requiresRenderTargetFlipping = true; caps.retina = context->isMainFramebufferRetina(); caps.ext3 = false; caps.extVertexArrayObject = (GLEW_VERSION_3_0 || GLEW_ARB_vertex_array_object || GLEW_APPLE_vertex_array_object); caps.extTextureStorage = (GLEW_VERSION_4_2 || GLEW_ARB_texture_storage); caps.extMapBuffer = true; caps.extMapBufferRange = (GLEW_VERSION_3_0 || GLEW_ARB_map_buffer_range); caps.extTimerQuery = !!GLEW_EXT_timer_query; caps.extDebugMarkers = false; caps.extSync = (GLEW_VERSION_3_2 || GLEW_ARB_sync); #ifdef _WIN32 const char* vendorString = reinterpret_cast(glGetString(GL_VENDOR)); if (strstr(vendorString, "Intel")) { // Intel drivers don't store IB state as part of VAO (http://stackoverflow.com/questions/8973690/vao-and-element-array-buffer-state) caps.extVertexArrayObject = false; } if (strstr(vendorString, "ATI Technologies")) { // Some AMD drivers can't create cubemaps with TS and can create 3D textures that they can't update afterwards... caps.extTextureStorage = false; } #endif return caps; } static DeviceCapsGL createDeviceCaps(ContextGL* context, const std::set& extensions) { if (!FFlag::GraphicsGL3) return createDeviceCapsOld(context); const char* version = reinterpret_cast(glGetString(GL_VERSION)); bool version3 = isdigit(version[0]) && version[0] >= '3'; DeviceCapsGL caps; GLint texSize = 0; glGetIntegerv(GL_MAX_TEXTURE_SIZE, &texSize); GLint stencilBits = 0; glGetIntegerv(GL_STENCIL_BITS, &stencilBits); caps.supportsFramebuffer = version3 || (extensions.count("GL_ARB_framebuffer_object") || extensions.count("GL_EXT_framebuffer_object")); caps.supportsShaders = version3 || GLEW_VERSION_2_0 || // need this for Mac OpenGL 1.4 :-/ (extensions.count("GL_ARB_shading_language_100") && extensions.count("GL_ARB_shader_objects") && extensions.count("GL_ARB_fragment_shader") && extensions.count("GL_ARB_vertex_shader")); caps.supportsFFP = false; caps.supportsStencil = GLEW_VERSION_2_0 && stencilBits >= 8; // we need full two-sided stencil support caps.supportsIndex32 = true; caps.supportsTextureDXT = !!extensions.count("GL_EXT_texture_compression_s3tc"); caps.supportsTexturePVR = false; caps.supportsTextureHalfFloat = version3; caps.supportsTexture3D = true; caps.supportsTextureETC1 = false; caps.supportsTexturePartialMipChain = true; caps.supportsTextureNPOT = version3; if (version3) { GLint drawBuffers = 0; glGetIntegerv(GL_MAX_DRAW_BUFFERS, &drawBuffers); GLint samples = 0; glGetIntegerv(GL_MAX_SAMPLES, &samples); caps.maxDrawBuffers = drawBuffers; caps.maxSamples = samples; } else { caps.maxDrawBuffers = 1; caps.maxSamples = 1; } caps.maxTextureSize = texSize; caps.maxTextureUnits = 16; caps.colorOrderBGR = false; caps.needsHalfPixelOffset = false; caps.requiresRenderTargetFlipping = true; caps.retina = context->isMainFramebufferRetina(); caps.ext3 = version3; caps.extVertexArrayObject = version3; caps.extTextureStorage = GLEW_VERSION_4_2 || (version3 && extensions.count("GL_ARB_texture_storage")); caps.extMapBuffer = true; caps.extMapBufferRange = version3; caps.extTimerQuery = !!GLEW_VERSION_3_3; caps.extDebugMarkers = false; caps.extSync = !!GLEW_VERSION_3_2; return caps; } static void GLAPIENTRY debugOutputGLARB(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const GLvoid* userParam) { FASTLOGS(FLog::Graphics, "GL Debug: %s", message); } #endif static DeviceVRGL* createVR() { #if defined(RBX_PLATFORM_IOS) || defined(__ANDROID__) if (DeviceVRGL* result = DeviceVRGL::createCardboard()) return result; #endif return NULL; } DeviceGL::DeviceGL(void* windowHandle) : frameTimeQueryId(0) , frameTimeQueryIssued(false) , frameEventQueryId(0) , frameEventQueryIssued(false) , gpuTime(0) , vrEnabled(true) { glContext.reset(ContextGL::create(windowHandle)); if (FFlag::RenderVR) vr.reset(createVR()); std::set extensions = getExtensions(); caps = createDeviceCaps(glContext.get(), extensions); FASTLOGS(FLog::Graphics, "GL Renderer: %s", reinterpret_cast(glGetString(GL_RENDERER))); FASTLOGS(FLog::Graphics, "GL Version: %s", reinterpret_cast(glGetString(GL_VERSION))); FASTLOGS(FLog::Graphics, "GL Vendor: %s", reinterpret_cast(glGetString(GL_VENDOR))); if (caps.supportsShaders) FASTLOGS(FLog::Graphics, "GLSL version: %s", reinterpret_cast(glGetString(GL_SHADING_LANGUAGE_VERSION))); std::string extensionString; for (auto& e: extensions) { extensionString += " "; extensionString += e; } while (!extensionString.empty()) { std::string::size_type space = extensionString.find_first_of(' ', 128); FASTLOGS(FLog::Graphics, "Ext:%s", extensionString.substr(0, space)); extensionString.erase(0, space); } caps.dumpToFLog(FLog::Graphics); FASTLOG1(FLog::Graphics, "Caps: GL3 %d", caps.ext3); FASTLOG5(FLog::Graphics, "Caps: VAO %d TexStorage %d MapBuffer %d MapBufferRange %d TimerQuery %d", caps.extVertexArrayObject, caps.extTextureStorage, caps.extMapBuffer, caps.extMapBufferRange, caps.extTimerQuery); FASTLOG2(FLog::Graphics, "Caps: DebugMarkers %d Sync %d", caps.extDebugMarkers, caps.extSync); immediateContext.reset(new DeviceContextGL(this)); mainFramebuffer.reset(new FramebufferGL(this, glContext->getMainFramebufferId())); std::pair dimensions = glContext->updateMainFramebuffer(0, 0); mainFramebuffer->updateDimensions(dimensions.first, dimensions.second); #ifndef GLES if (caps.extTimerQuery) { glGenQueries(1, &frameTimeQueryId); RBXASSERT(frameTimeQueryId); } if (FFlag::DebugGraphicsGL && GLEW_ARB_debug_output) { glDebugMessageCallbackARB(debugOutputGLARB, NULL); } #endif #ifndef _WIN32 Profiler::gpuInit(0); #endif if (vr) vr->setup(this); } DeviceGL::~DeviceGL() { #ifndef _WIN32 Profiler::gpuShutdown(); #endif vr.reset(); immediateContext.reset(); mainFramebuffer.reset(); glContext.reset(); } std::string DeviceGL::getFeatureLevel() { std::string glString = reinterpret_cast(glGetString(GL_VERSION)); std::string featureLevel = "OpenGL "; if (!glString.empty()) { size_t dotId = glString.find('.'); // this is based on GLEW implementation of determining GL version. Does it look unsafe? Dont worry, GLEW version is even worse;) if (dotId > 0 && dotId < (glString.length() -1) && isdigit(glString[dotId-1]) && isdigit(glString[dotId+1])) { featureLevel += glString[dotId - 1]; featureLevel += '.'; featureLevel += glString[dotId + 1]; return featureLevel; } } featureLevel += "unknown"; return featureLevel; } bool DeviceGL::validate() { glContext->setCurrent(); std::pair dimensions = glContext->updateMainFramebuffer(mainFramebuffer->getWidth(), mainFramebuffer->getHeight()); mainFramebuffer->updateDimensions(dimensions.first, dimensions.second); return true; } DeviceContext* DeviceGL::beginFrame() { glContext->setCurrent(); #ifndef GLES if (frameTimeQueryId && !frameTimeQueryIssued) { glBeginQuery(GL_TIME_ELAPSED, frameTimeQueryId); } #endif immediateContext->bindFramebuffer(mainFramebuffer.get()); immediateContext->clearStates(); return immediateContext.get(); } void DeviceGL::endFrame() { #ifndef GLES if (frameTimeQueryId) { if (!frameTimeQueryIssued) { glEndQuery(GL_TIME_ELAPSED); frameTimeQueryIssued = true; } else { int available = 0; glGetQueryObjectiv(frameTimeQueryId, GL_QUERY_RESULT_AVAILABLE, &available); if (available) { GLuint64EXT elapsed = 0; if (FFlag::GraphicsGL3) glGetQueryObjectui64v(frameTimeQueryId, GL_QUERY_RESULT, &elapsed); else glGetQueryObjectui64vEXT(frameTimeQueryId, GL_QUERY_RESULT, &elapsed); gpuTime = static_cast(elapsed) / 1e6f; frameTimeQueryIssued = false; } } } #endif if (FFlag::GraphicsGLReduceLatency && caps.extSync) { // Wait for last frame to finish on GPU if (frameEventQueryIssued) { int rc = glClientWaitSync(frameEventQueryId, GL_SYNC_FLUSH_COMMANDS_BIT, GLuint64(5e9)); RBXASSERT(rc == GL_CONDITION_SATISFIED || rc == GL_ALREADY_SIGNALED); glDeleteSync(frameEventQueryId); frameEventQueryId = 0; frameEventQueryIssued = false; } // Submit new query for frame wait if (!frameEventQueryIssued) { frameEventQueryId = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); RBXASSERT(frameEventQueryId); frameEventQueryIssued = true; } } if (vr && vrEnabled) { vr->submitFrame(immediateContext.get()); } glContext->swapBuffers(); } Framebuffer* DeviceGL::getMainFramebuffer() { return mainFramebuffer.get(); } DeviceVR* DeviceGL::getVR() { return (vr && vrEnabled) ? vr.get() : NULL; } void DeviceGL::setVR(bool enabled) { vrEnabled = enabled; } void DeviceGL::defineGlobalConstants(size_t dataSize, const std::vector& constants) { RBXASSERT(globalConstants.empty()); RBXASSERT(!constants.empty()); globalConstants = constants; immediateContext->defineGlobalConstants(dataSize); } std::string DeviceGL::getShadingLanguage() { #ifdef GLES return caps.ext3 ? "glsles3" : "glsles"; #else return caps.ext3 ? "glsl3" : "glsl"; #endif } std::string DeviceGL::createShaderSource(const std::string& path, const std::string& defines, boost::function fileCallback) { // No preprocessor support return fileCallback(path); } std::vector DeviceGL::createShaderBytecode(const std::string& source, const std::string& target, const std::string& entrypoint) { // No bytecode support RBXASSERT(entrypoint == "main"); return std::vector(source.begin(), source.end()); } shared_ptr DeviceGL::createVertexShader(const std::vector& bytecode) { if (!caps.supportsShaders) throw RBX::runtime_error("No shader support"); std::string source(bytecode.begin(), bytecode.end()); return shared_ptr(new VertexShaderGL(this, source)); } shared_ptr DeviceGL::createFragmentShader(const std::vector& bytecode) { if (!caps.supportsShaders) throw RBX::runtime_error("No shader support"); std::string source(bytecode.begin(), bytecode.end()); return shared_ptr(new FragmentShaderGL(this, source)); } shared_ptr DeviceGL::createShaderProgram(const shared_ptr& vertexShader, const shared_ptr& fragmentShader) { if (!caps.supportsShaders) throw RBX::runtime_error("No shader support"); return shared_ptr(new ShaderProgramGL(this, vertexShader, fragmentShader)); } shared_ptr DeviceGL::createShaderProgramFFP() { throw RBX::runtime_error("No FFP support"); } shared_ptr DeviceGL::createVertexBuffer(size_t elementSize, size_t elementCount, VertexBuffer::Usage usage) { return shared_ptr(new VertexBufferGL(this, elementSize, elementCount, usage)); } shared_ptr DeviceGL::createIndexBuffer(size_t elementSize, size_t elementCount, VertexBuffer::Usage usage) { return shared_ptr(new IndexBufferGL(this, elementSize, elementCount, usage)); } shared_ptr DeviceGL::createVertexLayout(const std::vector& elements) { return shared_ptr(new VertexLayoutGL(this, elements)); } shared_ptr DeviceGL::createTexture(Texture::Type type, Texture::Format format, unsigned int width, unsigned int height, unsigned int depth, unsigned int mipLevels, Texture::Usage usage) { return shared_ptr(new TextureGL(this, type, format, width, height, depth, mipLevels, usage)); } shared_ptr DeviceGL::createRenderbuffer(Texture::Format format, unsigned int width, unsigned int height, unsigned int samples) { return shared_ptr(new RenderbufferGL(this, format, width, height, samples)); } shared_ptr DeviceGL::createGeometryImpl(const shared_ptr& layout, const std::vector >& vertexBuffers, const shared_ptr& indexBuffer, unsigned int baseVertexIndex) { return shared_ptr(new GeometryGL(this, layout, vertexBuffers, indexBuffer, baseVertexIndex)); } shared_ptr DeviceGL::createFramebufferImpl(const std::vector >& color, const shared_ptr& depth) { return shared_ptr(new FramebufferGL(this, color, depth)); } DeviceStats DeviceGL::getStatistics() const { DeviceStats result = {}; result.gpuFrameTime = gpuTime; return result; } } }