Store and load data of compressed textures on GLES

Since GLES does not support glGetCompressedTexImage function, the
compressed data cannot be extracted. For solving this, we store
them at glCompressedTexImage* calls, and use these data when
required (however it is not necessarily correct).
This commit is contained in:
Janos Pantos
2017-05-22 02:55:54 -07:00
committed by Baldur Karlsson
parent 9ef362c28e
commit 70514fc5ac
8 changed files with 296 additions and 165 deletions
+9
View File
@@ -279,6 +279,11 @@ private:
// bound to the second.
GLuint renderbufferReadTex;
GLuint renderbufferFBOs[2];
// since compressed textures cannot be read back on GLES we have to store them during the
// uploading
// level -> data
map<int, vector<byte> > compressedData;
};
map<ResourceId, TextureData> m_Textures;
@@ -517,6 +522,10 @@ private:
vector<string> m_GLExtensions;
vector<string> m_GLESExtensions;
void StoreCompressedTexData(ResourceId texId, GLenum target, GLint level, GLint xoffset,
GLint yoffset, GLint zoffset, GLsizei width, GLsizei height,
GLsizei depth, GLenum format, GLsizei imageSize, const void *pixels);
// no copy semantics
WrappedOpenGL(const WrappedOpenGL &);
WrappedOpenGL &operator=(const WrappedOpenGL &);
+121 -162
View File
@@ -562,6 +562,102 @@ bool GLResourceManager::Prepare_InitialState(GLResource res)
return true;
}
void GLResourceManager::CreateTextureImage(GLuint tex, GLenum internalFormat, GLenum textype,
GLint dim, GLint width, GLint height, GLint depth,
GLint samples, int mips)
{
const GLHookSet &gl = m_GL->GetHookset();
if(textype == eGL_TEXTURE_BUFFER)
{
return;
}
else if(textype == eGL_TEXTURE_2D_MULTISAMPLE)
{
gl.glTextureStorage2DMultisampleEXT(tex, textype, samples, internalFormat, width, height,
GL_TRUE);
}
else if(textype == eGL_TEXTURE_2D_MULTISAMPLE_ARRAY)
{
gl.glTextureStorage3DMultisampleEXT(tex, textype, samples, internalFormat, width, height, depth,
GL_TRUE);
}
else
{
gl.glTextureParameteriEXT(tex, textype, eGL_TEXTURE_MAX_LEVEL, mips - 1);
bool isCompressed = IsCompressedFormat(internalFormat);
GLenum baseFormat = eGL_RGBA;
GLenum dataType = eGL_UNSIGNED_BYTE;
if(!isCompressed)
{
baseFormat = GetBaseFormat(internalFormat);
dataType = GetDataType(internalFormat);
}
GLenum targets[] = {
eGL_TEXTURE_CUBE_MAP_POSITIVE_X, eGL_TEXTURE_CUBE_MAP_NEGATIVE_X,
eGL_TEXTURE_CUBE_MAP_POSITIVE_Y, eGL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
eGL_TEXTURE_CUBE_MAP_POSITIVE_Z, eGL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
};
int count = ARRAY_COUNT(targets);
if(textype != eGL_TEXTURE_CUBE_MAP)
{
targets[0] = textype;
count = 1;
}
GLsizei w = (GLsizei)width;
GLsizei h = (GLsizei)height;
GLsizei d = (GLsizei)depth;
for(int m = 0; m < mips; m++)
{
for(int t = 0; t < count; t++)
{
if(isCompressed)
{
GLsizei compSize = (GLsizei)GetCompressedByteSize(w, h, d, internalFormat);
vector<byte> dummy;
dummy.resize(compSize);
if(dim == 1)
gl.glCompressedTextureImage1DEXT(tex, targets[t], m, internalFormat, w, 0, compSize,
&dummy[0]);
else if(dim == 2)
gl.glCompressedTextureImage2DEXT(tex, targets[t], m, internalFormat, w, h, 0, compSize,
&dummy[0]);
else if(dim == 3)
gl.glCompressedTextureImage3DEXT(tex, targets[t], m, internalFormat, w, h, d, 0,
compSize, &dummy[0]);
}
else
{
if(dim == 1)
gl.glTextureImage1DEXT(tex, targets[t], m, internalFormat, w, 0, baseFormat, dataType,
NULL);
else if(dim == 2)
gl.glTextureImage2DEXT(tex, targets[t], m, internalFormat, w, h, 0, baseFormat,
dataType, NULL);
else if(dim == 3)
gl.glTextureImage3DEXT(tex, targets[t], m, internalFormat, w, h, d, 0, baseFormat,
dataType, NULL);
}
}
w = RDCMAX(1, w >> 1);
if(textype != eGL_TEXTURE_1D_ARRAY)
h = RDCMAX(1, h >> 1);
if(textype != eGL_TEXTURE_2D_ARRAY && textype != eGL_TEXTURE_CUBE_MAP_ARRAY)
d = RDCMAX(1, d >> 1);
}
}
}
void GLResourceManager::PrepareTextureInitialContents(ResourceId liveid, ResourceId origid,
GLResource res)
{
@@ -671,95 +767,16 @@ void GLResourceManager::PrepareTextureInitialContents(ResourceId liveid, Resourc
gl.glBindTexture(details.curType, oldtex);
}
int depth = details.depth;
if(details.curType != eGL_TEXTURE_3D)
depth = 1;
int mips =
GetNumMips(gl, details.curType, res.name, details.width, details.height, details.depth);
GLenum baseFormat = eGL_RGBA;
GLenum dataType = eGL_UNSIGNED_BYTE;
if(!IsCompressedFormat(details.internalFormat))
{
baseFormat = GetBaseFormat(details.internalFormat);
dataType = GetDataType(details.internalFormat);
}
if(details.curType == eGL_TEXTURE_2D_MULTISAMPLE ||
details.curType == eGL_TEXTURE_2D_MULTISAMPLE_ARRAY)
mips = 1;
// create texture of identical format/size to store initial contents
if(details.curType == eGL_TEXTURE_2D_MULTISAMPLE)
{
gl.glTextureStorage2DMultisampleEXT(tex, details.curType, details.samples,
details.internalFormat, details.width, details.height,
GL_TRUE);
mips = 1;
}
else if(details.curType == eGL_TEXTURE_2D_MULTISAMPLE_ARRAY)
{
gl.glTextureStorage3DMultisampleEXT(tex, details.curType, details.samples,
details.internalFormat, details.width, details.height,
details.depth, GL_TRUE);
mips = 1;
}
else if(details.dimension == 1)
{
gl.glTextureParameteriEXT(tex, details.curType, eGL_TEXTURE_MAX_LEVEL, mips - 1);
int w = details.width;
for(int i = 0; i < mips; i++)
{
gl.glTextureImage1DEXT(tex, details.curType, i, details.internalFormat, w, 0, baseFormat,
dataType, NULL);
w = RDCMAX(1, w >> 1);
}
}
else if(details.dimension == 2)
{
GLenum targets[] = {
eGL_TEXTURE_CUBE_MAP_POSITIVE_X, eGL_TEXTURE_CUBE_MAP_NEGATIVE_X,
eGL_TEXTURE_CUBE_MAP_POSITIVE_Y, eGL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
eGL_TEXTURE_CUBE_MAP_POSITIVE_Z, eGL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
};
gl.glTextureParameteriEXT(tex, details.curType, eGL_TEXTURE_MAX_LEVEL, mips - 1);
int w = details.width;
int h = details.height;
for(int i = 0; i < mips; i++)
{
if(details.curType != eGL_TEXTURE_CUBE_MAP)
{
gl.glTextureImage2DEXT(tex, details.curType, i, details.internalFormat, w, h, 0,
baseFormat, dataType, NULL);
}
else
{
for(size_t t = 0; t < ARRAY_COUNT(targets); t++)
gl.glTextureImage2DEXT(tex, targets[t], i, details.internalFormat, w, h, 0,
baseFormat, dataType, NULL);
}
w = RDCMAX(1, w >> 1);
if(details.curType != eGL_TEXTURE_1D_ARRAY)
h = RDCMAX(1, h >> 1);
}
}
else if(details.dimension == 3)
{
gl.glTextureParameteriEXT(tex, details.curType, eGL_TEXTURE_MAX_LEVEL, mips - 1);
int w = details.width;
int h = details.height;
int d = details.depth;
for(int i = 0; i < mips; i++)
{
gl.glTextureImage3DEXT(tex, details.curType, i, details.internalFormat, w, h, d, 0,
baseFormat, dataType, NULL);
w = RDCMAX(1, w >> 1);
h = RDCMAX(1, h >> 1);
if(details.curType == eGL_TEXTURE_3D)
d = RDCMAX(1, d >> 1);
}
}
CreateTextureImage(tex, details.internalFormat, details.curType, details.dimension,
details.width, details.height, details.depth, details.samples, mips);
// we need to set maxlevel appropriately for number of mips to force the texture to be
// complete.
@@ -1164,7 +1181,21 @@ bool GLResourceManager::Serialise_InitialState(ResourceId resid, GLResource res)
byte *buf = new byte[size];
gl.glGetCompressedTextureImageEXT(tex, targets[trg], i, buf);
if(IsGLES)
{
const vector<byte> &data = details.compressedData[i];
const byte *src =
(count == 1) ? data.data() : data.data() + CubeTargetIndex(targets[trg]) * size;
size_t storedSize = data.size() / count;
if(storedSize == size)
memcpy(buf, src, size);
else
RDCERR("Different expected and stored compressed texture sizes!");
}
else
{
gl.glGetCompressedTextureImageEXT(tex, targets[trg], i, buf);
}
m_pSerialiser->SerialiseBuffer("image", buf, size);
@@ -1378,90 +1409,18 @@ bool GLResourceManager::Serialise_InitialState(ResourceId resid, GLResource res)
GLenum dummy;
EmulateLuminanceFormat(gl, tex, textype, internalformat, dummy);
GLenum baseFormat = eGL_RGBA;
GLenum dataType = eGL_UNSIGNED_BYTE;
if(!IsCompressedFormat(internalformat))
{
baseFormat = GetBaseFormat(internalformat);
dataType = GetDataType(internalformat);
}
// create texture of identical format/size to store initial contents
if(textype == eGL_TEXTURE_BUFFER || details.view)
{
// no 'contents' texture to create
}
else if(textype == eGL_TEXTURE_2D_MULTISAMPLE)
else
{
gl.glTextureStorage2DMultisampleEXT(tex, textype, samples, internalformat, width, height,
GL_TRUE);
CreateTextureImage(tex, internalformat, textype, dim, width, height, depth, samples, mips);
}
if(textype == eGL_TEXTURE_2D_MULTISAMPLE || textype == eGL_TEXTURE_2D_MULTISAMPLE_ARRAY)
mips = 1;
}
else if(textype == eGL_TEXTURE_2D_MULTISAMPLE_ARRAY)
{
gl.glTextureStorage3DMultisampleEXT(tex, textype, samples, internalformat, width, height,
depth, GL_TRUE);
mips = 1;
}
else if(dim == 1)
{
gl.glTextureParameteriEXT(tex, textype, eGL_TEXTURE_MAX_LEVEL, mips - 1);
int w = width;
for(int i = 0; i < mips; i++)
{
gl.glTextureImage1DEXT(tex, textype, i, internalformat, w, 0, baseFormat, dataType, NULL);
w = RDCMAX(1, w >> 1);
}
}
else if(dim == 2)
{
GLenum targets[] = {
eGL_TEXTURE_CUBE_MAP_POSITIVE_X, eGL_TEXTURE_CUBE_MAP_NEGATIVE_X,
eGL_TEXTURE_CUBE_MAP_POSITIVE_Y, eGL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
eGL_TEXTURE_CUBE_MAP_POSITIVE_Z, eGL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
};
gl.glTextureParameteriEXT(tex, textype, eGL_TEXTURE_MAX_LEVEL, mips - 1);
int w = width;
int h = height;
for(int i = 0; i < mips; i++)
{
if(textype != eGL_TEXTURE_CUBE_MAP)
{
gl.glTextureImage2DEXT(tex, textype, i, internalformat, w, h, 0, baseFormat, dataType,
NULL);
}
else
{
for(size_t t = 0; t < ARRAY_COUNT(targets); t++)
gl.glTextureImage2DEXT(tex, targets[t], i, internalformat, w, h, 0, baseFormat,
dataType, NULL);
}
w = RDCMAX(1, w >> 1);
if(textype != eGL_TEXTURE_1D_ARRAY)
h = RDCMAX(1, h >> 1);
}
}
else if(dim == 3)
{
gl.glTextureParameteriEXT(tex, textype, eGL_TEXTURE_MAX_LEVEL, mips - 1);
int w = width;
int h = height;
int d = depth;
for(int i = 0; i < mips; i++)
{
gl.glTextureImage3DEXT(tex, textype, i, internalformat, w, h, d, 0, baseFormat,
dataType, NULL);
w = RDCMAX(1, w >> 1);
h = RDCMAX(1, h >> 1);
if(textype == eGL_TEXTURE_3D)
d = RDCMAX(1, d >> 1);
}
}
if(textype == eGL_TEXTURE_BUFFER || details.view)
{
+2
View File
@@ -220,6 +220,8 @@ private:
bool Need_InitialStateChunk(GLResource res);
bool Prepare_InitialState(GLResource res);
void CreateTextureImage(GLuint tex, GLenum internalFormat, GLenum textype, GLint dim, GLint width,
GLint height, GLint depth, GLint samples, int mips);
void PrepareTextureInitialContents(ResourceId liveid, ResourceId origid, GLResource res);
void Create_InitialState(ResourceId id, GLResource live, bool hasData);
+24 -2
View File
@@ -2525,7 +2525,18 @@ byte *GLReplay::GetTextureData(ResourceId tex, uint32_t arrayIdx, uint32_t mip,
if(m_GetTexturePrevData[mip] == NULL)
{
m_GetTexturePrevData[mip] = new byte[dataSize * arraysize];
gl.glGetCompressedTexImage(target, mip, m_GetTexturePrevData[mip]);
if(IsGLES)
{
const vector<byte> &data = texDetails.compressedData[mip];
if(data.size() == dataSize * arraysize)
memcpy(m_GetTexturePrevData[mip], data.data(), data.size());
else
RDCERR("Different expected and stored compressed texture sizes for array texture!");
}
else
{
gl.glGetCompressedTexImage(target, mip, m_GetTexturePrevData[mip]);
}
}
// now copy the slice from the cache into ret
@@ -2537,7 +2548,18 @@ byte *GLReplay::GetTextureData(ResourceId tex, uint32_t arrayIdx, uint32_t mip,
else
{
// for non-arrays we can just readback without caching
gl.glGetCompressedTexImage(target, mip, ret);
if(IsGLES)
{
const vector<byte> &data = texDetails.compressedData[mip];
if(data.size() == dataSize)
memcpy(m_GetTexturePrevData[mip], data.data(), data.size());
else
RDCERR("Different expected and stored compressed texture sizes!");
}
else
{
gl.glGetCompressedTexImage(target, mip, m_GetTexturePrevData[mip]);
}
}
}
else
+16
View File
@@ -996,6 +996,22 @@ GLenum FramebufferBinding(GLenum target)
return eGL_NONE;
}
bool IsCubeFace(GLenum target)
{
switch(target)
{
case eGL_TEXTURE_CUBE_MAP_POSITIVE_X:
case eGL_TEXTURE_CUBE_MAP_NEGATIVE_X:
case eGL_TEXTURE_CUBE_MAP_POSITIVE_Y:
case eGL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
case eGL_TEXTURE_CUBE_MAP_POSITIVE_Z:
case eGL_TEXTURE_CUBE_MAP_NEGATIVE_Z: return true;
default: break;
}
return false;
}
GLint CubeTargetIndex(GLenum face)
{
switch(face)
+1
View File
@@ -58,6 +58,7 @@ bool IsUIntFormat(GLenum internalFormat);
bool IsSIntFormat(GLenum internalFormat);
bool IsSRGBFormat(GLenum internalFormat);
bool IsCubeFace(GLenum target);
GLint CubeTargetIndex(GLenum face);
GLenum TextureBinding(GLenum target);
GLenum TextureTarget(GLenum target);
+9 -1
View File
@@ -1138,7 +1138,15 @@ void APIENTRY _glCopyImageSubData(GLuint srcName, GLenum srcTarget, GLint srcLev
for(int trg = 0; trg < count; trg++)
{
// read to CPU
hookset->glGetCompressedTextureImageEXT(srcName, targets[trg], srcLevel, buf);
if(IsGLES)
{
RDCERR("Can't emulate glCopyImageSubData without glGetCompressedTexImage on GLES");
memset(buf, 0, size);
}
else
{
hookset->glGetCompressedTextureImageEXT(srcName, targets[trg], srcLevel, buf);
}
// write to GPU
if(srcTarget == eGL_TEXTURE_1D || srcTarget == eGL_TEXTURE_1D_ARRAY)
@@ -2816,6 +2816,104 @@ void WrappedOpenGL::glCompressedMultiTexImage1DEXT(GLenum texunit, GLenum target
}
}
void WrappedOpenGL::StoreCompressedTexData(ResourceId texId, GLenum target, GLint level,
GLint xoffset, GLint yoffset, GLint zoffset,
GLsizei width, GLsizei height, GLsizei depth,
GLenum format, GLsizei imageSize, const void *pixels)
{
byte *unpackedPixels = NULL;
byte *srcPixels = NULL;
GLint unpackbuf = 0;
m_Real.glGetIntegerv(eGL_PIXEL_UNPACK_BUFFER_BINDING, &unpackbuf);
if(unpackbuf == 0 && pixels != NULL)
{
PixelUnpackState unpack;
unpack.Fetch(&m_Real, false);
if(unpack.FastPathCompressed(width, height, depth))
srcPixels = (byte *)pixels;
else
srcPixels = unpackedPixels =
unpack.UnpackCompressed((byte *)pixels, width, height, depth, imageSize);
}
if(unpackbuf != 0)
srcPixels = (byte *)m_Real.glMapBufferRange(eGL_PIXEL_UNPACK_BUFFER, (GLintptr)pixels,
imageSize, eGL_MAP_READ_BIT);
if(srcPixels)
{
string error;
// Only the trivial case is handled yet.
if(xoffset == 0 && yoffset == 0)
{
if(target == GL_TEXTURE_2D || target == GL_TEXTURE_CUBE_MAP_POSITIVE_X ||
target == GL_TEXTURE_CUBE_MAP_NEGATIVE_X || target == GL_TEXTURE_CUBE_MAP_POSITIVE_Y ||
target == GL_TEXTURE_CUBE_MAP_NEGATIVE_Y || target == GL_TEXTURE_CUBE_MAP_POSITIVE_Z ||
target == GL_TEXTURE_CUBE_MAP_NEGATIVE_Z || target == GL_TEXTURE_2D_ARRAY ||
target == GL_TEXTURE_CUBE_MAP_ARRAY)
{
if(depth <= 1)
{
size_t compressedImageSize = GetCompressedByteSize(width, height, 1, format);
RDCASSERT(compressedImageSize == (size_t)imageSize);
auto &cd = m_Textures[texId].compressedData;
auto &cdData = cd[level];
GLint zoff = IsCubeFace(target) ? CubeTargetIndex(target) : zoffset;
size_t startOffset = imageSize * zoff;
if(cdData.size() < startOffset + imageSize)
cdData.resize(startOffset + imageSize);
memcpy(cdData.data() + startOffset, srcPixels, imageSize);
}
else
{
error = StringFormat::Fmt("depth (%d)", depth);
}
}
else if(target == GL_TEXTURE_3D)
{
if(zoffset == 0)
{
RDCASSERT(GetCompressedByteSize(width, height, depth, format) == (size_t)imageSize);
auto &cd = m_Textures[texId].compressedData;
auto &cdData = cd[level];
cdData.resize(imageSize);
memcpy(cdData.data(), srcPixels, imageSize);
}
else
{
error = StringFormat::Fmt("zoffset (%d)", zoffset);
}
}
else
{
error = "target";
}
}
else
{
error = StringFormat::Fmt("xoffset (%d) and/or yoffset (%d)", xoffset, yoffset);
}
if(unpackbuf != 0)
m_Real.glUnmapBuffer(eGL_PIXEL_UNPACK_BUFFER);
if(!error.empty())
RDCWARN("StoreCompressedTexData: Unexpected %s (tex:%llu, target:%s)", error.c_str(), texId,
ToStr::Get(target).c_str());
}
else
{
RDCWARN("StoreCompressedTexData: No source pixels to copy from (tex:%llu, target:%s)", texId,
ToStr::Get(target).c_str());
}
SAFE_DELETE_ARRAY(unpackedPixels);
}
bool WrappedOpenGL::Serialise_glCompressedTextureImage2DEXT(GLuint texture, GLenum target,
GLint level, GLenum internalformat,
GLsizei width, GLsizei height,
@@ -2951,6 +3049,10 @@ void WrappedOpenGL::Common_glCompressedTextureImage2DEXT(ResourceId texId, GLenu
GLResourceRecord *record = GetResourceManager()->GetResourceRecord(texId);
RDCASSERT(record);
if(IsGLES)
StoreCompressedTexData(record->GetResourceID(), target, level, 0, 0, 0, width, height, 0,
internalformat, imageSize, pixels);
// This is kind of an arbitary heuristic, but in the past a game has re-specified a texture with
// glTexImage over and over
// so we need to attempt to catch the case where glTexImage is called to re-upload data, not
@@ -3176,6 +3278,10 @@ void WrappedOpenGL::Common_glCompressedTextureImage3DEXT(ResourceId texId, GLenu
GLResourceRecord *record = GetResourceManager()->GetResourceRecord(texId);
RDCASSERT(record);
if(IsGLES)
StoreCompressedTexData(record->GetResourceID(), target, level, 0, 0, 0, width, height, depth,
internalformat, imageSize, pixels);
// This is kind of an arbitary heuristic, but in the past a game has re-specified a texture with
// glTexImage over and over
// so we need to attempt to catch the case where glTexImage is called to re-upload data, not
@@ -5166,6 +5272,10 @@ void WrappedOpenGL::Common_glCompressedTextureSubImage2DEXT(GLResourceRecord *re
GLint unpackbuf = 0;
m_Real.glGetIntegerv(eGL_PIXEL_UNPACK_BUFFER_BINDING, &unpackbuf);
if(IsGLES)
StoreCompressedTexData(record->GetResourceID(), target, level, xoffset, yoffset, 0, width,
height, 0, format, imageSize, pixels);
if(m_State == WRITING_IDLE && unpackbuf != 0)
{
GetResourceManager()->MarkDirtyResource(record->GetResourceID());
@@ -5360,6 +5470,10 @@ void WrappedOpenGL::Common_glCompressedTextureSubImage3DEXT(GLResourceRecord *re
GLint unpackbuf = 0;
m_Real.glGetIntegerv(eGL_PIXEL_UNPACK_BUFFER_BINDING, &unpackbuf);
if(IsGLES)
StoreCompressedTexData(record->GetResourceID(), target, level, xoffset, yoffset, zoffset, width,
height, depth, format, imageSize, pixels);
if(m_State == WRITING_IDLE && unpackbuf != 0)
{
GetResourceManager()->MarkDirtyResource(record->GetResourceID());