/* * Copyright (C) 2017 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. */ #include "GLcommon/SaveableTexture.h" #include "base/ArraySize.h" #include "base/SmallVector.h" #include "base/StreamSerializing.h" #include "base/System.h" #include "GLcommon/GLEScontext.h" #include "GLcommon/GLutils.h" #include "GLcommon/TextureUtils.h" #include "host-common/crash_reporter.h" #include "host-common/logging.h" #include #define SAVEABLE_TEXTURE_DEBUG 0 #if SAVEABLE_TEXTURE_DEBUG #define D(fmt,...) printf("%s:%d " fmt "\n", __func__, __LINE__, ##__VA_ARGS__); #else #define D(fmt,...) #endif // using android::base::ScopedMemoryProfiler; // using android::base::LazyInstance; // using android::base::MemoryProfiler; // using android::base::StringView; static const GLenum kTexParam[] = { GL_TEXTURE_MIN_FILTER, GL_TEXTURE_MAG_FILTER, GL_TEXTURE_WRAP_S, GL_TEXTURE_WRAP_T, }; static const GLenum kTexParamGles3[] = { GL_TEXTURE_BASE_LEVEL, GL_TEXTURE_COMPARE_FUNC, GL_TEXTURE_COMPARE_MODE, GL_TEXTURE_MIN_LOD, GL_TEXTURE_MAX_LOD, GL_TEXTURE_MAX_LEVEL, GL_TEXTURE_SWIZZLE_R, GL_TEXTURE_SWIZZLE_G, GL_TEXTURE_SWIZZLE_B, GL_TEXTURE_SWIZZLE_A, GL_TEXTURE_WRAP_R, }; static uint32_t s_texAlign(uint32_t v, uint32_t align) { uint32_t rem = v % align; return rem ? (v + (align - rem)) : v; } // s_computePixelSize is both in the host and the guest. Consider moving it to // android-emugl/shared static int s_computePixelSize(GLenum format, GLenum type) { #define FORMAT_ERROR(format, type) \ fprintf(stderr, "%s:%d unknown format/type 0x%x 0x%x\n", __FUNCTION__, \ __LINE__, format, type); switch (type) { case GL_BYTE: switch (format) { case GL_R8: case GL_R8I: case GL_R8_SNORM: case GL_RED: return 1; case GL_RED_INTEGER: return 1; case GL_RG8: case GL_RG8I: case GL_RG8_SNORM: case GL_RG: return 1 * 2; case GL_RG_INTEGER: return 1 * 2; case GL_RGB8: case GL_RGB8I: case GL_RGB8_SNORM: case GL_RGB: return 1 * 3; case GL_RGB_INTEGER: return 1 * 3; case GL_RGBA8: case GL_RGBA8I: case GL_RGBA8_SNORM: case GL_RGBA: return 1 * 4; case GL_RGBA_INTEGER: return 1 * 4; default: FORMAT_ERROR(format, type); } break; case GL_UNSIGNED_BYTE: switch (format) { case GL_R8: case GL_R8UI: case GL_RED: return 1; case GL_RED_INTEGER: return 1; case GL_ALPHA8_EXT: case GL_ALPHA: return 1; case GL_LUMINANCE8_EXT: case GL_LUMINANCE: return 1; case GL_LUMINANCE8_ALPHA8_EXT: case GL_LUMINANCE_ALPHA: return 1 * 2; case GL_RG8: case GL_RG8UI: case GL_RG: return 1 * 2; case GL_RG_INTEGER: return 1 * 2; case GL_RGB8: case GL_RGB8UI: case GL_SRGB8: case GL_RGB: return 1 * 3; case GL_RGB_INTEGER: return 1 * 3; case GL_RGBA8: case GL_RGBA8UI: case GL_SRGB8_ALPHA8: case GL_RGBA: return 1 * 4; case GL_RGBA_INTEGER: return 1 * 4; case GL_BGRA_EXT: case GL_BGRA8_EXT: return 1 * 4; default: FORMAT_ERROR(format, type); } break; case GL_SHORT: switch (format) { case GL_R16I: case GL_RED_INTEGER: return 2; case GL_RG16I: case GL_RG_INTEGER: return 2 * 2; case GL_RGB16I: case GL_RGB_INTEGER: return 2 * 3; case GL_RGBA16I: case GL_RGBA_INTEGER: return 2 * 4; default: FORMAT_ERROR(format, type); } break; case GL_UNSIGNED_SHORT: switch (format) { case GL_DEPTH_COMPONENT16: case GL_DEPTH_COMPONENT: return 2; case GL_R16UI: case GL_RED_INTEGER: return 2; case GL_RG16UI: case GL_RG_INTEGER: return 2 * 2; case GL_RGB16UI: case GL_RGB_INTEGER: return 2 * 3; case GL_RGBA16UI: case GL_RGBA_INTEGER: return 2 * 4; default: FORMAT_ERROR(format, type); } break; case GL_INT: switch (format) { case GL_R32I: case GL_RED_INTEGER: return 4; case GL_RG32I: case GL_RG_INTEGER: return 4 * 2; case GL_RGB32I: case GL_RGB_INTEGER: return 4 * 3; case GL_RGBA32I: case GL_RGBA_INTEGER: return 4 * 4; default: FORMAT_ERROR(format, type); } break; case GL_UNSIGNED_INT: switch (format) { case GL_DEPTH_COMPONENT16: case GL_DEPTH_COMPONENT24: case GL_DEPTH_COMPONENT32_OES: case GL_DEPTH_COMPONENT: return 4; case GL_R32UI: case GL_RED_INTEGER: return 4; case GL_RG32UI: case GL_RG_INTEGER: return 4 * 2; case GL_RGB32UI: case GL_RGB_INTEGER: return 4 * 3; case GL_RGBA32UI: case GL_RGBA_INTEGER: return 4 * 4; default: FORMAT_ERROR(format, type); } break; case GL_UNSIGNED_SHORT_4_4_4_4: case GL_UNSIGNED_SHORT_5_5_5_1: case GL_UNSIGNED_SHORT_5_6_5: case GL_UNSIGNED_SHORT_4_4_4_4_REV_EXT: case GL_UNSIGNED_SHORT_1_5_5_5_REV_EXT: return 2; case GL_UNSIGNED_INT_10F_11F_11F_REV: case GL_UNSIGNED_INT_5_9_9_9_REV: case GL_UNSIGNED_INT_2_10_10_10_REV: case GL_UNSIGNED_INT_24_8_OES: return 4; case GL_FLOAT_32_UNSIGNED_INT_24_8_REV: return 4 + 4; case GL_FLOAT: switch (format) { case GL_DEPTH_COMPONENT32F: case GL_DEPTH_COMPONENT: return 4; case GL_ALPHA32F_EXT: case GL_ALPHA: return 4; case GL_LUMINANCE32F_EXT: case GL_LUMINANCE: return 4; case GL_LUMINANCE_ALPHA32F_EXT: case GL_LUMINANCE_ALPHA: return 4 * 2; case GL_RED: return 4; case GL_R32F: return 4; case GL_RG: return 4 * 2; case GL_RG32F: return 4 * 2; case GL_RGB: return 4 * 3; case GL_RGB32F: return 4 * 3; case GL_RGBA: return 4 * 4; case GL_RGBA32F: return 4 * 4; default: FORMAT_ERROR(format, type); } break; case GL_HALF_FLOAT: case GL_HALF_FLOAT_OES: switch (format) { case GL_ALPHA16F_EXT: case GL_ALPHA: return 2; case GL_LUMINANCE16F_EXT: case GL_LUMINANCE: return 2; case GL_LUMINANCE_ALPHA16F_EXT: case GL_LUMINANCE_ALPHA: return 2 * 2; case GL_RED: return 2; case GL_R16F: return 2; case GL_RG: return 2 * 2; case GL_RG16F: return 2 * 2; case GL_RGB: return 2 * 3; case GL_RGB16F: return 2 * 3; case GL_RGBA: return 2 * 4; case GL_RGBA16F: return 2 * 4; default: FORMAT_ERROR(format, type); } break; default: FORMAT_ERROR(format, type); } return 0; } static uint32_t s_texImageSize(GLenum internalformat, GLenum type, int unpackAlignment, GLsizei width, GLsizei height) { uint32_t alignedWidth = s_texAlign(width, unpackAlignment); uint32_t pixelSize = s_computePixelSize(internalformat, type); uint32_t totalSize = pixelSize * alignedWidth * height; return totalSize; } struct TextureDataReader { GLESVersion glesVersion = GLES_2_0; GLenum fbTarget = GL_FRAMEBUFFER; GLint prevViewport[4] = { 0, 0, 0, 0 }; GLuint fbo = 0; GLuint prevFbo = 0; void setupFbo() { GLenum fbBindingTarget; auto gl = GLEScontext::dispatcher(); glesVersion = gl.getGLESVersion(); if (glesVersion >= GLES_3_0) { fbTarget = GL_READ_FRAMEBUFFER; fbBindingTarget = GL_READ_FRAMEBUFFER_BINDING; } else { fbTarget = GL_FRAMEBUFFER; fbBindingTarget = GL_FRAMEBUFFER_BINDING; } gl.glGetIntegerv(GL_VIEWPORT, prevViewport); gl.glGenFramebuffers(1, &fbo); gl.glGetIntegerv(fbBindingTarget, (GLint*)&prevFbo); gl.glBindFramebuffer(fbTarget, fbo); } void teardownFbo() { if (!fbo) return; auto gl = GLEScontext::dispatcher(); gl.glBindFramebuffer(fbTarget, prevFbo); gl.glDeleteFramebuffers(1, &fbo); gl.glViewport(prevViewport[0], prevViewport[1], prevViewport[2], prevViewport[3]); *this = {}; } bool shouldUseReadPixels( GLenum target, GLenum level, GLenum format, GLenum type) { auto gl = GLEScontext::dispatcher(); if (!gl.glGetTexImage) return true; // TODO: if (isGles2Gles()) return true // TODO: Query extensions for support for these kinds of things if (target != GL_TEXTURE_2D || level != 0) return false; #define KNOWN_GOOD_READ_PIXELS_COMBINATION(goodFormat, goodType) \ if (format == goodFormat && type == goodType) return true; KNOWN_GOOD_READ_PIXELS_COMBINATION(GL_RGB, GL_UNSIGNED_BYTE) KNOWN_GOOD_READ_PIXELS_COMBINATION(GL_RGBA, GL_UNSIGNED_BYTE) return false; } void getTexImage( GLuint globalName, GLenum target, GLenum level, GLenum format, GLenum type, GLint width, GLint height, GLint depth, uint8_t* data) { D("Reading: %u 0x%x %u 0x%x 0x%x %d x %d x %d...", globalName, target, level, format, type, width, height, depth); auto gl = GLEScontext::dispatcher(); if (!shouldUseReadPixels(target, level, format, type)) { D("with underlying glGetTexImage"); gl.glGetTexImage(target, level, format, type, data); return; } GLenum attachment = GL_COLOR_ATTACHMENT0; switch (format) { case GL_DEPTH_COMPONENT: attachment = GL_DEPTH_ATTACHMENT; break; case GL_DEPTH_STENCIL: attachment = GL_DEPTH_STENCIL_ATTACHMENT; break; } gl.glViewport(0, 0, width, height); switch (target) { case GL_TEXTURE_2D: case GL_TEXTURE_CUBE_MAP_POSITIVE_X: case GL_TEXTURE_CUBE_MAP_NEGATIVE_X: case GL_TEXTURE_CUBE_MAP_POSITIVE_Y: case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y: case GL_TEXTURE_CUBE_MAP_POSITIVE_Z: case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z: D("with glFramebufferTexture2D + glReadPixels"); gl.glFramebufferTexture2D( fbTarget, attachment, target, globalName, level); gl.glReadPixels(0, 0, width, height, format, type, data); gl.glFramebufferTexture2D( fbTarget, attachment, target, 0, level); break; case GL_TEXTURE_3D: { D("with glFramebufferTexture3DOES + glReadPixels"); unsigned int layerImgSize = s_texImageSize( format, type, 1, width, height); for (unsigned int d = 0; d < depth; d++) { gl.glFramebufferTexture3DOES( fbTarget, attachment, target, globalName, level, d); gl.glReadPixels(0, 0, width, height, format, type, data + layerImgSize * d); gl.glFramebufferTexture3DOES( fbTarget, attachment, target, 0, level, d); } break; } case GL_TEXTURE_2D_ARRAY: { D("with glFramebufferTextureLayer + glReadPixels"); unsigned int layerImgSize = s_texImageSize( format, type, 1, width, height); for (unsigned int d = 0; d < depth; d++) { gl.glFramebufferTextureLayer( fbTarget, attachment, globalName, level, d); gl.glReadPixels(0, 0, width, height, format, type, data + layerImgSize * d); gl.glFramebufferTextureLayer( fbTarget, attachment, 0, level, d); } break; } } } void preSave() { setupFbo(); } void postSave() { teardownFbo(); } }; static TextureDataReader* sTextureDataReader() { static TextureDataReader* r = new TextureDataReader; return r; } void SaveableTexture::preSave() { sTextureDataReader()->preSave(); } void SaveableTexture::postSave() { sTextureDataReader()->postSave(); } SaveableTexture::SaveableTexture(const TextureData& texture) : m_target(texture.target), m_width(texture.width), m_height(texture.height), m_depth(texture.depth), m_format(texture.format), m_internalFormat(texture.internalFormat), m_type(texture.type), m_border(texture.border), m_texStorageLevels(texture.texStorageLevels), m_globalName(texture.getGlobalName()), m_isDirty(true) {} SaveableTexture::SaveableTexture(GlobalNameSpace* globalNameSpace, loader_t&& loader) : m_loader(std::move(loader)), m_globalNamespace(globalNameSpace), m_isDirty(false) { mNeedRestore = true; } void SaveableTexture::loadFromStream(android::base::Stream* stream) { m_target = stream->getBe32(); m_width = stream->getBe32(); m_height = stream->getBe32(); m_depth = stream->getBe32(); m_format = stream->getBe32(); m_internalFormat = stream->getBe32(); m_type = stream->getBe32(); m_border = stream->getBe32(); m_texStorageLevels = stream->getBe32(); m_maxMipmapLevel = stream->getBe32(); // TODO: handle other texture targets if (m_target == GL_TEXTURE_2D || m_target == GL_TEXTURE_CUBE_MAP || m_target == GL_TEXTURE_3D || m_target == GL_TEXTURE_2D_ARRAY) { unsigned int numLevels = m_texStorageLevels ? m_texStorageLevels : m_maxMipmapLevel + 1; auto loadTex = [stream, numLevels]( std::unique_ptr& levelData, bool isDepth) { levelData.reset(new LevelImageData[numLevels]); for (unsigned int level = 0; level < numLevels; level++) { levelData[level].m_width = stream->getBe32(); levelData[level].m_height = stream->getBe32(); if (isDepth) { levelData[level].m_depth = stream->getBe32(); } loadBuffer(stream, &levelData[level].m_data); } }; switch (m_target) { case GL_TEXTURE_2D: loadTex(m_levelData[0], false); break; case GL_TEXTURE_CUBE_MAP: for (int i = 0; i < 6; i++) { loadTex(m_levelData[i], false); } break; case GL_TEXTURE_3D: case GL_TEXTURE_2D_ARRAY: loadTex(m_levelData[0], true); break; default: break; } // Load tex param loadCollection(stream, &m_texParam, [](android::base::Stream* stream) -> std::unordered_map::value_type { GLenum pname = stream->getBe32(); GLint value = stream->getBe32(); return std::make_pair(pname, value); }); } else if (m_target != 0) { GL_LOG("SaveableTexture::%s: warning: texture target 0x%x not " "supported\n", __func__, m_target); fprintf(stderr, "Warning: texture target %d not supported\n", m_target); } m_loadedFromStream.store(true); } void SaveableTexture::onSave( android::base::Stream* stream) { stream->putBe32(m_target); stream->putBe32(m_width); stream->putBe32(m_height); stream->putBe32(m_depth); stream->putBe32(m_format); stream->putBe32(m_internalFormat); stream->putBe32(m_type); stream->putBe32(m_border); stream->putBe32(m_texStorageLevels); stream->putBe32(m_maxMipmapLevel); // TODO: handle other texture targets if (m_target == GL_TEXTURE_2D || m_target == GL_TEXTURE_CUBE_MAP || m_target == GL_TEXTURE_3D || m_target == GL_TEXTURE_2D_ARRAY) { static constexpr GLenum pixelStoreIndexes[] = { GL_PACK_ROW_LENGTH, GL_PACK_SKIP_PIXELS, GL_PACK_SKIP_ROWS, GL_PACK_ALIGNMENT, }; static constexpr GLint pixelStoreDesired[] = {0, 0, 0, 1}; GLint pixelStorePrev[android::base::arraySize(pixelStoreIndexes)]; GLint prevTex = 0; GLDispatch& dispatcher = GLEScontext::dispatcher(); assert(dispatcher.glGetIntegerv); for (int i = 0; i != android::base::arraySize(pixelStoreIndexes); ++i) { if (isGles2Gles() && pixelStoreIndexes[i] != GL_PACK_ALIGNMENT && pixelStoreIndexes[i] != GL_UNPACK_ALIGNMENT) { continue; } dispatcher.glGetIntegerv(pixelStoreIndexes[i], &pixelStorePrev[i]); if (pixelStorePrev[i] != pixelStoreDesired[i]) { dispatcher.glPixelStorei(pixelStoreIndexes[i], pixelStoreDesired[i]); } } switch (m_target) { case GL_TEXTURE_2D: dispatcher.glGetIntegerv(GL_TEXTURE_BINDING_2D, &prevTex); break; case GL_TEXTURE_CUBE_MAP: dispatcher.glGetIntegerv(GL_TEXTURE_BINDING_CUBE_MAP, &prevTex); break; case GL_TEXTURE_3D: dispatcher.glGetIntegerv(GL_TEXTURE_BINDING_3D, &prevTex); break; case GL_TEXTURE_2D_ARRAY: dispatcher.glGetIntegerv(GL_TEXTURE_BINDING_2D_ARRAY, &prevTex); break; default: break; } dispatcher.glBindTexture(m_target, getGlobalName()); // Get the number of mipmap levels. unsigned int numLevels = m_texStorageLevels ? m_texStorageLevels : m_maxMipmapLevel + 1; // bug: 112749908 // Texture saving causes hundreds of megabytes of memory ballooning. // This could be behind nullptr dereferences in crash reports if // the user ran out of commit charge on Windows, which is not measured // in android::base::System::isUnderMemoryPressure. // // To debug this issue, avoid keeping the imgData buffers around, // and log the memory usage. // // bool isLowMem = android::base::System::isUnderMemoryPressure(); bool isLowMem = true; auto saveTex = [this, stream, numLevels, &dispatcher, isLowMem]( GLenum target, bool isDepth, std::unique_ptr& imgData) { if (m_isDirty) { imgData.reset(new LevelImageData[numLevels]); for (unsigned int level = 0; level < numLevels; level++) { unsigned int& width = imgData.get()[level].m_width; unsigned int& height = imgData.get()[level].m_height; unsigned int& depth = imgData.get()[level].m_depth; width = level == 0 ? m_width : std::max( imgData.get()[level - 1].m_width / 2, 1); height = level == 0 ? m_height : std::max( imgData.get()[level - 1].m_height / 2, 1); depth = level == 0 ? m_depth : std::max( imgData.get()[level - 1].m_depth / 2, 1); // ScopedMemoryProfiler::Callback memoryProfilerCallback = // [this, level, width, height, depth] // (StringView tag, StringView stage, // MemoryProfiler::MemoryUsageBytes currentResident, // MemoryProfiler::MemoryUsageBytes change) { // double megabyte = 1024.0 * 1024.0; // GL_LOG("%s %s: %f mb current. change: %f mb. texture:" // "format 0x%x type 0x%x level 0x%x dims (%u, %u, %u)\n", // c_str(tag).get(), // c_str(stage).get(), // (double)currentResident / megabyte, // (double)change / megabyte, // m_format, // m_type, // level, // width, height, depth); // }; // ScopedMemoryProfiler mem("saveTexture", memoryProfilerCallback); android::base::SmallFixedVector& buffer = imgData.get()[level].m_data; if (!isGles2Gles()) { GLint glWidth; GLint glHeight; dispatcher.glGetTexLevelParameteriv(target, level, GL_TEXTURE_WIDTH, &glWidth); dispatcher.glGetTexLevelParameteriv(target, level, GL_TEXTURE_HEIGHT, &glHeight); width = static_cast(glWidth); height = static_cast(glHeight); } if (isDepth) { if (!isGles2Gles()) { GLint glDepth; dispatcher.glGetTexLevelParameteriv(target, level, GL_TEXTURE_DEPTH, &glDepth); depth = static_cast(std::max(glDepth, 1)); } } else { depth = 1; } // Snapshot texture data buffer.clear(); buffer.resize_noinit( s_texImageSize(m_format, m_type, 1, width, height) * depth); if (!buffer.empty()) { GLenum neededBufferFormat = m_format; if (isCoreProfile()) { neededBufferFormat = getCoreProfileEmulatedFormat(m_format); } sTextureDataReader()->getTexImage( m_globalName, target, level, neededBufferFormat, m_type, width, height, depth, buffer.data()); } } } for (unsigned int level = 0; level < numLevels; level++) { stream->putBe32(imgData.get()[level].m_width); stream->putBe32(imgData.get()[level].m_height); if (isDepth) { stream->putBe32(imgData.get()[level].m_depth); } saveBuffer(stream, imgData.get()[level].m_data); } // If under memory pressure, delete this intermediate buffer. if (isLowMem) { imgData.reset(); } }; switch (m_target) { case GL_TEXTURE_2D: saveTex(GL_TEXTURE_2D, false, m_levelData[0]); break; case GL_TEXTURE_CUBE_MAP: saveTex(GL_TEXTURE_CUBE_MAP_POSITIVE_X, false, m_levelData[0]); saveTex(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, false, m_levelData[1]); saveTex(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, false, m_levelData[2]); saveTex(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, false, m_levelData[3]); saveTex(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, false, m_levelData[4]); saveTex(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, false, m_levelData[5]); break; case GL_TEXTURE_3D: saveTex(GL_TEXTURE_3D, true, m_levelData[0]); break; case GL_TEXTURE_2D_ARRAY: saveTex(GL_TEXTURE_2D_ARRAY, true, m_levelData[0]); break; default: break; } // Snapshot texture param TextureSwizzle emulatedBaseSwizzle; if (isCoreProfile()) { emulatedBaseSwizzle = getSwizzleForEmulatedFormat(m_format); } std::unordered_map texParam; auto saveParam = [this, &texParam, &dispatcher, emulatedBaseSwizzle]( const GLenum* plist, size_t plistSize) { GLint param; for (size_t i = 0; i < plistSize; i++) { dispatcher.glGetTexParameteriv(m_target, plist[i], ¶m); if (isSwizzleParam(plist[i]) && param != GL_ZERO && param != GL_ONE) { if (param == emulatedBaseSwizzle.toRed) { param = GL_RED; } else if (param == emulatedBaseSwizzle.toGreen) { param = GL_GREEN; } else if (param == emulatedBaseSwizzle.toBlue) { param = GL_BLUE; } else if (param == emulatedBaseSwizzle.toAlpha) { param = GL_ALPHA; } } texParam.emplace(plist[i], param); } }; saveParam(kTexParam, sizeof(kTexParam) / sizeof(kTexParam[0])); if (dispatcher.getGLESVersion() >= GLES_3_0) { saveParam(kTexParamGles3, sizeof(kTexParamGles3) / sizeof(kTexParamGles3[0])); } saveCollection(stream, texParam, [](android::base::Stream* s, const std::unordered_map::value_type& pair) { s->putBe32(pair.first); s->putBe32(pair.second); }); // Restore environment for (int i = 0; i != android::base::arraySize(pixelStoreIndexes); ++i) { if (isGles2Gles() && pixelStoreIndexes[i] != GL_PACK_ALIGNMENT && pixelStoreIndexes[i] != GL_UNPACK_ALIGNMENT) { continue; } if (pixelStorePrev[i] != pixelStoreDesired[i]) { dispatcher.glPixelStorei(pixelStoreIndexes[i], pixelStorePrev[i]); } } dispatcher.glBindTexture(m_target, prevTex); // If we were under memory pressure, we deleted the intermediate // buffer, so we need to maintain the invariant that m_isDirty = false // textures requires that the intermediate buffer is still around. // Therefore, mark as dirty if we were under memory pressure. // // TODO: Don't keep those around in memory regardless of memory // pressure m_isDirty = false || isLowMem; } else if (m_target != 0) { // SaveableTexture is uninitialized iff a texture hasn't been bound, // which will give m_target==0 GL_LOG("SaveableTexture::onSave: warning: texture target 0x%x not supported\n", m_target); fprintf(stderr, "Warning: texture target 0x%x not supported\n", m_target); } } void SaveableTexture::restore() { assert(m_loader); m_loader(this); if (!m_loadedFromStream.load()) { return; } m_globalTexObj.reset(new NamedObject( GenNameInfo(NamedObjectType::TEXTURE), m_globalNamespace)); if (!m_globalTexObj) { GL_LOG("SaveableTexture::%s: %p: could not allocate NamedObject for texture\n", __func__, this); emugl_crash_reporter( "Fatal: could not allocate SaveableTexture m_globalTexObj\n"); } m_globalName = m_globalTexObj->getGlobalName(); if (m_target == GL_TEXTURE_2D || m_target == GL_TEXTURE_CUBE_MAP || m_target == GL_TEXTURE_3D || m_target == GL_TEXTURE_2D_ARRAY) { // restore the texture GLDispatch& dispatcher = GLEScontext::dispatcher(); // Make sure we are using the right dispatcher assert(dispatcher.glGetIntegerv); static constexpr GLenum pixelStoreIndexes[] = { GL_UNPACK_ROW_LENGTH, GL_UNPACK_IMAGE_HEIGHT, GL_UNPACK_SKIP_PIXELS, GL_UNPACK_SKIP_ROWS, GL_UNPACK_SKIP_IMAGES, GL_UNPACK_ALIGNMENT, }; static constexpr GLint pixelStoreDesired[] = {0, 0, 0, 0, 0, 1}; GLint pixelStorePrev[android::base::arraySize(pixelStoreIndexes)]; for (int i = 0; i != android::base::arraySize(pixelStoreIndexes); ++i) { if (isGles2Gles() && pixelStoreIndexes[i] != GL_PACK_ALIGNMENT && pixelStoreIndexes[i] != GL_UNPACK_ALIGNMENT) { continue; } dispatcher.glGetIntegerv(pixelStoreIndexes[i], &pixelStorePrev[i]); if (pixelStorePrev[i] != pixelStoreDesired[i]) { dispatcher.glPixelStorei(pixelStoreIndexes[i], pixelStoreDesired[i]); } } GLint prevTex = 0; switch (m_target) { case GL_TEXTURE_2D: dispatcher.glGetIntegerv(GL_TEXTURE_BINDING_2D, &prevTex); break; case GL_TEXTURE_CUBE_MAP: dispatcher.glGetIntegerv(GL_TEXTURE_BINDING_CUBE_MAP, &prevTex); break; case GL_TEXTURE_3D: dispatcher.glGetIntegerv(GL_TEXTURE_BINDING_3D, &prevTex); break; case GL_TEXTURE_2D_ARRAY: dispatcher.glGetIntegerv(GL_TEXTURE_BINDING_2D_ARRAY, &prevTex); break; default: break; } dispatcher.glBindTexture(m_target, getGlobalName()); // Restore texture data dispatcher.glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // Get the number of mipmap levels. unsigned int numLevels = m_texStorageLevels ? m_texStorageLevels : m_maxMipmapLevel + 1; GLint resultInternalFormat = m_internalFormat; GLenum resultFormat = m_format; // Desktop OpenGL doesn't support GL_BGRA_EXT as internal format. if (!isGles2Gles() && m_type == GL_UNSIGNED_BYTE && m_format == GL_BGRA_EXT && resultInternalFormat == GL_BGRA_EXT) { resultInternalFormat = GL_RGBA; } else if (isCoreProfile() && isCoreProfileEmulatedFormat(m_format)) { resultInternalFormat = getCoreProfileEmulatedInternalFormat( m_format, m_type); resultFormat = getCoreProfileEmulatedFormat(m_format); } if (m_texStorageLevels) { switch (m_target) { case GL_TEXTURE_2D: case GL_TEXTURE_CUBE_MAP: dispatcher.glTexStorage2D(m_target, m_texStorageLevels, m_internalFormat, m_levelData[0].get()[0].m_width, m_levelData[0].get()[0].m_height); break; case GL_TEXTURE_3D: case GL_TEXTURE_2D_ARRAY: dispatcher.glTexStorage3D(m_target, m_texStorageLevels, m_internalFormat, m_levelData[0].get()[0].m_width, m_levelData[0].get()[0].m_height, m_levelData[0].get()[0].m_depth); break; } } auto restoreTex2D = [this, numLevels, resultInternalFormat, resultFormat, &dispatcher]( GLenum target, std::unique_ptr& levelData) { for (unsigned int level = 0; level < numLevels; level++) { const void* pixels = levelData[level].m_data.empty() ? nullptr : levelData[level].m_data.data(); if (!level || pixels) { if (m_texStorageLevels) { dispatcher.glTexSubImage2D( target, level, 0, 0, levelData[level].m_width, levelData[level].m_height, resultFormat, m_type, pixels); } else { dispatcher.glTexImage2D( target, level, resultInternalFormat, levelData[level].m_width, levelData[level].m_height, m_border, resultFormat, m_type, pixels); } } } }; auto restoreTex3D = [this, numLevels, resultFormat, &dispatcher]( GLenum target, std::unique_ptr& levelData) { for (unsigned int level = 0; level < numLevels; level++) { const void* pixels = levelData[level].m_data.empty() ? nullptr : levelData[level].m_data.data(); if (!level || pixels) { if (m_texStorageLevels) { dispatcher.glTexSubImage3D( target, level, 0, 0, 0, levelData[level].m_width, levelData[level].m_height, levelData[level].m_depth, resultFormat, m_type, pixels); } else { dispatcher.glTexImage3D( target, level, m_internalFormat, levelData[level].m_width, levelData[level].m_height, levelData[level].m_depth, m_border, resultFormat, m_type, pixels); } } } }; switch (m_target) { case GL_TEXTURE_2D: restoreTex2D(GL_TEXTURE_2D, m_levelData[0]); break; case GL_TEXTURE_CUBE_MAP: restoreTex2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, m_levelData[0]); restoreTex2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, m_levelData[1]); restoreTex2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, m_levelData[2]); restoreTex2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, m_levelData[3]); restoreTex2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, m_levelData[4]); restoreTex2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, m_levelData[5]); break; case GL_TEXTURE_3D: case GL_TEXTURE_2D_ARRAY: restoreTex3D(m_target, m_levelData[0]); break; default: break; } // Restore tex param TextureSwizzle emulatedBaseSwizzle; if (isCoreProfile()) { emulatedBaseSwizzle = getSwizzleForEmulatedFormat(m_format); } for (const auto& param : m_texParam) { if (isSwizzleParam(param.first)) { GLenum hostEquivalentSwizzle = swizzleComponentOf(emulatedBaseSwizzle, param.second); dispatcher.glTexParameteri(m_target, param.first, hostEquivalentSwizzle); } else { dispatcher.glTexParameteri(m_target, param.first, param.second); } } m_texParam.clear(); // Restore environment for (int i = 0; i != android::base::arraySize(pixelStoreIndexes); ++i) { if (isGles2Gles() && pixelStoreIndexes[i] != GL_PACK_ALIGNMENT && pixelStoreIndexes[i] != GL_UNPACK_ALIGNMENT) { continue; } if (pixelStorePrev[i] != pixelStoreDesired[i]) { dispatcher.glPixelStorei(pixelStoreIndexes[i], pixelStorePrev[i]); } } dispatcher.glBindTexture(m_target, prevTex); } } const NamedObjectPtr& SaveableTexture::getGlobalObject() { touch(); return m_globalTexObj; } void SaveableTexture::fillEglImage(EglImage* eglImage) { touch(); eglImage->border = m_border; eglImage->format = m_format; eglImage->height = m_height; eglImage->globalTexObj = m_globalTexObj; eglImage->internalFormat = m_internalFormat; eglImage->type = m_type; eglImage->width = m_width; eglImage->texStorageLevels = m_texStorageLevels; eglImage->sync = nullptr; if (!eglImage->globalTexObj) { GL_LOG("%s: EGL image %p has no global texture object!\n", __func__, eglImage); } } void SaveableTexture::makeDirty() { m_isDirty = true; } bool SaveableTexture::isDirty() const { return m_isDirty; } void SaveableTexture::setTarget(GLenum target) { m_target = target; } void SaveableTexture::setMipmapLevelAtLeast(unsigned int level) { m_maxMipmapLevel = std::max(level, m_maxMipmapLevel); } unsigned int SaveableTexture::getGlobalName() { if (m_globalTexObj) { return m_globalTexObj->getGlobalName(); } return m_globalName; }