blob: 84928b115532c34136ab3a9785dfd3272d076e77 [file] [log] [blame]
/*
* 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 "aemu/base/ArraySize.h"
#include "aemu/base/containers/SmallVector.h"
#include "aemu/base/files/StreamSerializing.h"
#include "aemu/base/system/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 <algorithm>
#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<LevelImageData[]>& 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<GLenum, GLint>::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<LevelImageData[]>& 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<unsigned int>(
imgData.get()[level - 1].m_width / 2, 1);
height = level == 0 ? m_height :
std::max<unsigned int>(
imgData.get()[level - 1].m_height / 2, 1);
depth = level == 0 ? m_depth :
std::max<unsigned int>(
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<unsigned char, 16>& 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<unsigned int>(glWidth);
height = static_cast<unsigned int>(glHeight);
}
if (isDepth) {
if (!isGles2Gles()) {
GLint glDepth;
dispatcher.glGetTexLevelParameteriv(target, level,
GL_TEXTURE_DEPTH, &glDepth);
depth = static_cast<unsigned int>(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<GLenum, GLint> 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], &param);
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<GLenum, GLint>::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::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<LevelImageData[]>& 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<LevelImageData[]>& 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;
}