in renderdoc/driver/gl/wrappers/gl_emulated.cpp [2635:3517]
void APIENTRY _glGetTexImage(GLenum target, GLint level, const GLenum format, const GLenum type,
void *pixels)
{
switch(target)
{
case eGL_TEXTURE_1D:
case eGL_TEXTURE_1D_ARRAY:
RDCWARN("1d and 1d array textures are not supported by GLES");
return;
case eGL_TEXTURE_BUFFER:
// TODO implement this
GLNOTIMP("Reading pixels from texture buffer");
return;
default: break;
}
GLuint fbo = 0;
GL.glGenFramebuffers(1, &fbo);
PushPopFramebuffer(eGL_FRAMEBUFFER, fbo);
GLint width = 0, height = 0, depth = 0;
GL.glGetTexLevelParameteriv(target, level, eGL_TEXTURE_WIDTH, &width);
GL.glGetTexLevelParameteriv(target, level, eGL_TEXTURE_HEIGHT, &height);
GL.glGetTexLevelParameteriv(target, level, eGL_TEXTURE_DEPTH, &depth);
GLenum origInternalFormat = eGL_NONE;
GL.glGetTexLevelParameteriv(target, level, eGL_TEXTURE_INTERNAL_FORMAT,
(GLint *)&origInternalFormat);
GLint boundTexture = 0;
GL.glGetIntegerv(TextureBinding(target), (GLint *)&boundTexture);
GLuint readtex = boundTexture;
GLuint deltex = 0;
GLenum attachment = eGL_COLOR_ATTACHMENT0;
if(format == eGL_DEPTH_COMPONENT)
attachment = eGL_DEPTH_ATTACHMENT;
else if(format == eGL_STENCIL)
attachment = eGL_STENCIL_ATTACHMENT;
else if(format == eGL_DEPTH_STENCIL)
attachment = eGL_DEPTH_STENCIL_ATTACHMENT;
bool readDirectly = true;
bool depthFormat = false;
// we know luminance/alpha formats can't be read directly, so assume failure for them
if(format == eGL_LUMINANCE_ALPHA || format == eGL_LUMINANCE || format == eGL_ALPHA)
{
readDirectly = false;
}
// similarly for RGB8, pessimistically assume it can't be read from a framebuffer as not all
// drivers support it.
if(format == eGL_RGB && type == eGL_UNSIGNED_BYTE)
{
readDirectly = false;
}
if((format == eGL_DEPTH_COMPONENT && !HasExt[NV_read_depth]) ||
(format == eGL_STENCIL && !HasExt[NV_read_stencil]) ||
(format == eGL_DEPTH_STENCIL && !HasExt[NV_read_depth_stencil]))
{
readDirectly = false;
}
if(format == eGL_DEPTH_COMPONENT || format == eGL_STENCIL || format == eGL_DEPTH_STENCIL)
{
depthFormat = true;
}
// Qualcomm drivers seem to barf if we try to read from cubemap faces above X+ for mips 64x64 or
// smaller. In testing X+ works on any mip, and all faces work on larger mips, but since the
// driver seems completely unreliable in this area we enable the workaround blanket for all
// cubemap reads as different formats may break in different ways.
if(VendorCheck[VendorCheck_Qualcomm_emulate_cube_reads])
{
switch(target)
{
case eGL_TEXTURE_CUBE_MAP:
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:
RDCLOG("Forcing indirect read for cubemap face %s on level %d", ToStr(target).c_str(), level);
readDirectly = false;
break;
default: break;
}
}
// if we can't attach the texture to a framebuffer, we can't readpixels it directly
if(readDirectly)
{
switch(target)
{
case eGL_TEXTURE_3D:
case eGL_TEXTURE_2D_ARRAY:
case eGL_TEXTURE_CUBE_MAP_ARRAY:
case eGL_TEXTURE_2D_MULTISAMPLE_ARRAY:
GL.glFramebufferTextureLayer(eGL_FRAMEBUFFER, attachment, readtex, level, 0);
break;
case eGL_TEXTURE_CUBE_MAP:
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:
case eGL_TEXTURE_2D:
case eGL_TEXTURE_2D_MULTISAMPLE:
default:
GL.glFramebufferTexture2D(eGL_FRAMEBUFFER, attachment, target, readtex, level);
break;
}
GLenum status = GL.glCheckFramebufferStatus(eGL_FRAMEBUFFER);
readDirectly = (status == eGL_FRAMEBUFFER_COMPLETE);
}
GLenum readFormat = format;
GLenum readType = type;
ResourceFormat origFmt = MakeResourceFormat(target, origInternalFormat);
bool disableSRGBCorrect = false;
// do a blit to the nearest compatible expanded format if we can't read directly
if(!readDirectly)
{
ResourceFormat remappedFmt = origFmt;
// special case luminance/alpha
if(readFormat == eGL_ALPHA || readFormat == eGL_LUMINANCE ||
origFmt.type == ResourceFormatType::A8)
{
RDCASSERTEQUAL(origFmt.compType, CompType::UNorm);
RDCASSERTEQUAL(origFmt.compCount, 1);
RDCASSERTEQUAL(origFmt.compByteWidth, 1);
remappedFmt.compType = CompType::UNorm;
remappedFmt.compCount = 1;
remappedFmt.type = ResourceFormatType::Regular;
remappedFmt.compByteWidth = 1;
// we can read directly after the remap, because it's still a 1 component unorm texture it's
// just now in the right format
readDirectly = true;
}
else if(readFormat == eGL_LUMINANCE_ALPHA)
{
RDCASSERTEQUAL(origFmt.compType, CompType::UNorm);
RDCASSERTEQUAL(origFmt.compCount, 2);
RDCASSERTEQUAL(origFmt.compByteWidth, 1);
remappedFmt.compType = CompType::UNorm;
remappedFmt.compCount = 2;
remappedFmt.type = ResourceFormatType::Regular;
remappedFmt.compByteWidth = 1;
readDirectly = true;
}
else
{
if(depthFormat)
{
// all depth formats we read back as RGBA float
remappedFmt.compType = CompType::Float;
remappedFmt.compCount = 4;
remappedFmt.type = ResourceFormatType::Regular;
// try full floats
remappedFmt.compByteWidth = 4;
// unless it's not supported
if(!HasExt[OES_texture_float] && GLCoreVersion < 30)
{
remappedFmt.compByteWidth = 2;
RDCDEBUG("Implementation doesn't support float color targets, reading as half-float");
}
}
// for most regular formats we just try to remap to the 4-component version assuming that if
// the smaller version is supported then the larger version is supported and FBO'able. This
// should hold for RGB formats at least.
else if(origFmt.type == ResourceFormatType::Regular &&
(origFmt.compType == CompType::Float || origFmt.compType == CompType::UNorm ||
origFmt.compType == CompType::UInt || origFmt.compType == CompType::SInt ||
origFmt.compType == CompType::UNormSRGB))
{
remappedFmt.compCount = 4;
remappedFmt.SetBGRAOrder(false);
}
// for SNorm formats remap to RGBA16F as well and hope it's supported. This loses precision on
// RGBA16_SNORM but we accept that.
else if(origFmt.compType == CompType::SNorm)
{
remappedFmt.compType = CompType::Float;
remappedFmt.compCount = 4;
remappedFmt.type = ResourceFormatType::Regular;
remappedFmt.compByteWidth = 2;
}
// if it's a sub-1-byte unorm format, remap to RGBA8
else if(origFmt.type == ResourceFormatType::R4G4 ||
origFmt.type == ResourceFormatType::R4G4B4A4 ||
origFmt.type == ResourceFormatType::R5G5B5A1 ||
origFmt.type == ResourceFormatType::R5G6B5)
{
remappedFmt.compType = CompType::UNorm;
remappedFmt.compCount = 4;
remappedFmt.type = ResourceFormatType::Regular;
remappedFmt.compByteWidth = 1;
remappedFmt.SetBGRAOrder(false);
}
// similar with sub-16F special formats
else if(origFmt.type == ResourceFormatType::R10G10B10A2 && origFmt.compType == CompType::UNorm)
{
remappedFmt.compType = CompType::UNorm;
remappedFmt.compCount = 4;
remappedFmt.type = ResourceFormatType::Regular;
remappedFmt.compByteWidth = 1;
}
else if(origFmt.type == ResourceFormatType::R10G10B10A2 && origFmt.compType == CompType::UInt)
{
remappedFmt.compType = CompType::UInt;
remappedFmt.compCount = 4;
remappedFmt.type = ResourceFormatType::Regular;
remappedFmt.compByteWidth = 1;
}
else if(origFmt.type == ResourceFormatType::R11G11B10 ||
origFmt.type == ResourceFormatType::R9G9B9E5)
{
remappedFmt.compType = CompType::Float;
remappedFmt.compCount = 4;
remappedFmt.type = ResourceFormatType::Regular;
remappedFmt.compByteWidth = 2;
}
}
GLenum internalformat = MakeGLFormat(remappedFmt);
GLenum remapformat = GetBaseFormat(internalformat);
GLenum remaptype = GetDataType(internalformat);
RDCDEBUG("Doing manual blit from %s to %s with format %s and type %s to allow readback",
ToStr(origInternalFormat).c_str(), ToStr(internalformat).c_str(),
ToStr(format).c_str(), ToStr(type).c_str());
GLint baseLevel = 0;
GLint maxLevel = 0;
// for cubemaps we read them back face by face so we blit to a 2D texture, but we still need to
// bind the source texture as a cubemap
GLenum origTarget = target;
GLenum bindTarget = target;
switch(target)
{
case eGL_TEXTURE_CUBE_MAP:
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:
target = eGL_TEXTURE_2D;
bindTarget = eGL_TEXTURE_CUBE_MAP;
break;
default: break;
}
// sample from only the right level of the source texture
GL.glGetTexParameteriv(bindTarget, eGL_TEXTURE_BASE_LEVEL, &baseLevel);
GL.glGetTexParameteriv(bindTarget, eGL_TEXTURE_MAX_LEVEL, &maxLevel);
GL.glTexParameteri(bindTarget, eGL_TEXTURE_BASE_LEVEL, level);
GL.glTexParameteri(bindTarget, eGL_TEXTURE_MAX_LEVEL, level);
// support 2D/Array/3D textures for now
RDCASSERT((target == eGL_TEXTURE_2D) || (target == eGL_TEXTURE_2D_ARRAY) ||
(target == eGL_TEXTURE_3D),
target);
GL.glGenTextures(1, &readtex);
GL.glBindTexture(target, readtex);
// allocate the texture
GL.glTexParameteri(target, eGL_TEXTURE_MAX_LEVEL, 0);
if(target == eGL_TEXTURE_2D)
{
GL.glTexImage2D(target, 0, internalformat, width, height, 0, remapformat, remaptype, NULL);
GL.glFramebufferTexture2D(eGL_FRAMEBUFFER, eGL_COLOR_ATTACHMENT0, target, readtex, 0);
}
else
{
GL.glTexImage3D(target, 0, internalformat, width, height, depth, 0, remapformat, remaptype,
NULL);
GL.glFramebufferTextureLayer(eGL_FRAMEBUFFER, eGL_COLOR_ATTACHMENT0, readtex, 0, 0);
}
GLenum fbostatus = GL.glCheckFramebufferStatus(eGL_FRAMEBUFFER);
if(fbostatus != eGL_FRAMEBUFFER_COMPLETE)
RDCERR("glReadPixels emulation blit FBO is %s with format %s", ToStr(fbostatus).c_str(),
ToStr(internalformat).c_str());
// push rendering state
GLPushPopState textState;
textState.Push(true);
// render a blit triangle to move alpha in the source to red in the dest
GL.glDisable(eGL_BLEND);
GL.glDisable(eGL_DEPTH_TEST);
GL.glDisable(eGL_STENCIL_TEST);
GL.glDisable(eGL_CULL_FACE);
GL.glDisable(eGL_SCISSOR_TEST);
GL.glViewport(0, 0, width, height);
bool oldFBOsrgb = false;
if(HasExt[EXT_framebuffer_sRGB])
{
oldFBOsrgb = GL.glIsEnabled(eGL_FRAMEBUFFER_SRGB) == GL_TRUE;
GL.glEnable(eGL_FRAMEBUFFER_SRGB);
}
else if(origFmt.compType == CompType::UNormSRGB)
{
disableSRGBCorrect = true;
}
GL.glActiveTexture(eGL_TEXTURE0);
GL.glBindTexture(bindTarget, boundTexture);
GLuint prog;
{
const char *swizzle = "rgba";
if(format == eGL_ALPHA)
{
swizzle = "aaaa";
}
else if(format == eGL_LUMINANCE || depthFormat)
{
swizzle = "rrrr";
}
else if(format == eGL_LUMINANCE_ALPHA)
{
swizzle = "raaa";
}
rdcstr vssource;
rdcstr fssource;
if(bindTarget == eGL_TEXTURE_CUBE_MAP)
{
vssource =
"attribute vec2 pos;\n"
"void main() { gl_Position = vec4(pos, 0.5, 0.5); }";
rdcstr cubecoord = "\nvec3 CalcCubeCoord(vec2 uv) {\nuv -= vec2(0.5);\nvec3 coord;\n";
if(origTarget == eGL_TEXTURE_CUBE_MAP_POSITIVE_X)
cubecoord += "coord = vec3(0.5, -uv.y, -uv.x);\n";
else if(origTarget == eGL_TEXTURE_CUBE_MAP_NEGATIVE_X)
cubecoord += "coord = vec3(-0.5, -uv.y, uv.x);\n";
else if(origTarget == eGL_TEXTURE_CUBE_MAP_POSITIVE_Y)
cubecoord += "coord = vec3(uv.x, 0.5, uv.y);\n";
else if(origTarget == eGL_TEXTURE_CUBE_MAP_NEGATIVE_Y)
cubecoord += "coord = vec3(uv.x, -0.5, -uv.y);\n";
else if(origTarget == eGL_TEXTURE_CUBE_MAP_POSITIVE_Z)
cubecoord += "coord = vec3(uv.x, -uv.y, 0.5);\n";
else // origTarget == eGL_TEXTURE_CUBE_MAP_NEGATIVE_Z
cubecoord += "coord = vec3(-uv.x, -uv.y, -0.5);\n";
cubecoord += "\nreturn coord;\n}\n";
fssource = rdcstr(
"precision highp float;\n"
"uniform vec3 res;\n"
"uniform samplerCube srcTex;\n") +
cubecoord +
"void main() { gl_FragColor = textureCube(srcTex, "
"CalcCubeCoord(vec2(gl_FragCoord.xy)/res.xy))." +
swizzle + "; }";
}
else if(target == eGL_TEXTURE_2D)
{
vssource =
"attribute vec2 pos;\n"
"void main() { gl_Position = vec4(pos, 0.5, 0.5); }";
fssource =
rdcstr(
"precision highp float;\n"
"uniform vec3 res;\n"
"uniform sampler2D srcTex;\n"
"void main() { gl_FragColor = texture2D(srcTex, vec2(gl_FragCoord.xy)/res.xy).") +
swizzle + "; }";
}
else
{
vssource =
"#version 300 es\n"
"in vec2 pos;\n"
"void main() { gl_Position = vec4(pos, 0.5, 0.5); }";
const char *sampler = (target == eGL_TEXTURE_2D_ARRAY) ? "sampler2DArray" : "sampler3D";
fssource =
rdcstr(
"#version 300 es\n"
"uniform highp vec3 res;\n"
"uniform highp ") +
sampler +
" srcTex;\n"
"out highp vec4 color;\n"
"void main() { color = texture(srcTex, vec3(gl_FragCoord.xy/res.xy, res.z).xyz)." +
swizzle + "; }";
}
const char *vs = vssource.c_str();
const char *fs = fssource.c_str();
GLuint vert = GL.glCreateShader(eGL_VERTEX_SHADER);
GLuint frag = GL.glCreateShader(eGL_FRAGMENT_SHADER);
GL.glShaderSource(vert, 1, &vs, NULL);
GL.glShaderSource(frag, 1, &fs, NULL);
GL.glCompileShader(vert);
GL.glCompileShader(frag);
char buffer[1024] = {0};
GLint status = 0;
GL.glGetShaderiv(vert, eGL_COMPILE_STATUS, &status);
if(status == 0)
{
GL.glGetShaderInfoLog(vert, 1024, NULL, buffer);
RDCERR("Shader error: %s", buffer);
}
GL.glGetShaderiv(frag, eGL_COMPILE_STATUS, &status);
if(status == 0)
{
GL.glGetShaderInfoLog(frag, 1024, NULL, buffer);
RDCERR("Shader error: %s", buffer);
}
prog = GL.glCreateProgram();
GL.glAttachShader(prog, vert);
GL.glAttachShader(prog, frag);
GL.glLinkProgram(prog);
GL.glGetProgramiv(prog, eGL_LINK_STATUS, &status);
if(status == 0)
{
GL.glGetProgramInfoLog(prog, 1024, NULL, buffer);
RDCERR("Link error: %s", buffer);
}
GL.glDeleteShader(vert);
GL.glDeleteShader(frag);
}
// fullscreen triangle
float verts[] = {
-1.0f, -1.0f, // vertex 0
3.0f, -1.0f, // vertex 1
-1.0f, 3.0f, // vertex 2
};
GLuint vb = 0;
GL.glGenBuffers(1, &vb);
GL.glBindBuffer(eGL_ARRAY_BUFFER, vb);
GL.glBufferData(eGL_ARRAY_BUFFER, sizeof(verts), verts, eGL_STATIC_DRAW);
GLuint vao;
GL.glGenVertexArrays(1, &vao);
GL.glBindVertexArray(vao);
GLint loc = GL.glGetAttribLocation(prog, "pos");
GL.glVertexAttribPointer((GLuint)loc, 2, eGL_FLOAT, GL_FALSE, sizeof(float) * 2, 0);
GL.glEnableVertexAttribArray((GLuint)loc);
GL.glUseProgram(prog);
GLenum depthMode = eGL_DEPTH_COMPONENT;
if(depthFormat && HasExt[ARB_stencil_texturing])
{
GL.glGetTexParameteriv(bindTarget, eGL_DEPTH_STENCIL_TEXTURE_MODE, (GLint *)&depthMode);
}
for(int32_t d = 0; d < depth; ++d)
{
GL.glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
if(depthFormat && HasExt[ARB_stencil_texturing])
{
GL.glTexParameteri(bindTarget, eGL_DEPTH_STENCIL_TEXTURE_MODE, eGL_DEPTH_COMPONENT);
}
if(target != eGL_TEXTURE_2D)
{
GL.glFramebufferTextureLayer(eGL_FRAMEBUFFER, eGL_COLOR_ATTACHMENT0, readtex, 0, d);
}
loc = GL.glGetUniformLocation(prog, "srcTex");
GL.glUniform1i(loc, 0);
loc = GL.glGetUniformLocation(prog, "res");
GL.glUniform3f(loc, float(width), float(height), float(d));
GL.glDrawArrays(eGL_TRIANGLES, 0, 3);
// if we support reading stencil, read the stencil into green
if(remapformat == eGL_DEPTH_STENCIL && HasExt[ARB_stencil_texturing])
{
GL.glTexParameteri(bindTarget, eGL_DEPTH_STENCIL_TEXTURE_MODE, eGL_STENCIL_INDEX);
GL.glColorMask(GL_FALSE, GL_TRUE, GL_FALSE, GL_FALSE);
GL.glDrawArrays(eGL_TRIANGLES, 0, 3);
}
}
GL.glDeleteVertexArrays(1, &vao);
GL.glDeleteBuffers(1, &vb);
GL.glDeleteProgram(prog);
if(HasExt[EXT_framebuffer_sRGB] && !oldFBOsrgb)
GL.glDisable(eGL_FRAMEBUFFER_SRGB);
// pop rendering state
textState.Pop(true);
// delete this texture once we're done, at the end of the function
deltex = readtex;
// restore base/max level as we changed them to sample from the right level above
GL.glBindTexture(bindTarget, boundTexture);
GL.glTexParameteri(bindTarget, eGL_TEXTURE_BASE_LEVEL, baseLevel);
GL.glTexParameteri(bindTarget, eGL_TEXTURE_MAX_LEVEL, maxLevel);
if(depthFormat && HasExt[ARB_stencil_texturing])
{
GL.glTexParameteri(bindTarget, eGL_DEPTH_STENCIL_TEXTURE_MODE, depthMode);
}
// read from the blitted texture from level 0, as red
GL.glBindTexture(target, readtex);
level = 0;
readFormat = remapformat;
readType = remaptype;
attachment = eGL_COLOR_ATTACHMENT0;
RDCDEBUG("Done blit");
}
size_t dstSliceSize = GetByteSize(width, height, 1, format, type);
bool swizzleBGRA = false;
// if we can't read BGRA natively, read as RGBA and swizzle manually
if(!HasExt[EXT_read_format_bgra] && readFormat == eGL_BGRA)
{
readFormat = eGL_RGBA;
swizzleBGRA = true;
}
if(!readDirectly && readFormat == eGL_RGBA && readType == eGL_UNSIGNED_SHORT_4_4_4_4)
{
readType = eGL_UNSIGNED_BYTE;
swizzleBGRA = true;
}
for(GLint d = 0; d < depth; ++d)
{
switch(target)
{
case eGL_TEXTURE_3D:
case eGL_TEXTURE_2D_ARRAY:
case eGL_TEXTURE_CUBE_MAP_ARRAY:
case eGL_TEXTURE_2D_MULTISAMPLE_ARRAY:
GL.glFramebufferTextureLayer(eGL_FRAMEBUFFER, attachment, readtex, level, d);
break;
case eGL_TEXTURE_CUBE_MAP:
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:
case eGL_TEXTURE_2D:
case eGL_TEXTURE_2D_MULTISAMPLE:
default:
GL.glFramebufferTexture2D(eGL_FRAMEBUFFER, attachment, target, readtex, level);
break;
}
GLenum status = GL.glCheckFramebufferStatus(eGL_FRAMEBUFFER);
if(status != eGL_FRAMEBUFFER_COMPLETE)
RDCERR("glReadPixels emulation FBO is %s", ToStr(status).c_str());
byte *dst = (byte *)pixels + d * dstSliceSize;
// pick the read format/type
if((readFormat == eGL_RGBA && readType == eGL_UNSIGNED_BYTE) ||
(readFormat == eGL_RGBA_INTEGER && readType == eGL_UNSIGNED_INT) ||
(readFormat == eGL_RGBA_INTEGER && readType == eGL_INT) ||
(readFormat == eGL_RGBA && readType == eGL_UNSIGNED_INT_2_10_10_10_REV))
{
// if we were already planning to use one of the spec-guaranteed supported combinations,
// great! use that
}
else
{
// see what format and type the implementation supports
GLenum implFormat = eGL_NONE, implType = eGL_NONE;
if(!depthFormat)
{
GL.glGetIntegerv(eGL_IMPLEMENTATION_COLOR_READ_FORMAT, (GLint *)&implFormat);
GL.glGetIntegerv(eGL_IMPLEMENTATION_COLOR_READ_TYPE, (GLint *)&implType);
// GL_HALF_FLOAT and GL_HALF_FLOAT_OES have different enum values but are the same
// otherwise. we always use the normal enum ourselves, but if the driver wants the _OES
// version then just use that so we match and can do a direct readback.
if(implType == eGL_HALF_FLOAT_OES && readType == eGL_HALF_FLOAT)
readType = eGL_HALF_FLOAT_OES;
}
if(!depthFormat && implFormat == readFormat && implType == readType)
{
// great, the implementation supports the format and type we want
}
else
{
// need to remap now from what we read to what we need to write.
readDirectly = false;
// if all that's different is the number of components, read as one of the
// guaranteed-supported format/type pairs
if((readFormat == eGL_RGBA || readFormat == eGL_RGB || readFormat == eGL_RG ||
readFormat == eGL_RED) &&
(readType == eGL_UNSIGNED_BYTE || readType == eGL_UNSIGNED_SHORT_4_4_4_4))
{
readFormat = eGL_RGBA;
readType = eGL_UNSIGNED_BYTE;
}
else if((readFormat == eGL_RGBA_INTEGER || readFormat == eGL_RGB_INTEGER ||
readFormat == eGL_RG_INTEGER || readFormat == eGL_RED_INTEGER) &&
(readType == eGL_UNSIGNED_BYTE || readType == eGL_UNSIGNED_SHORT ||
readType == eGL_UNSIGNED_INT))
{
readFormat = eGL_RGBA_INTEGER;
readType = eGL_UNSIGNED_INT;
}
else if((readFormat == eGL_RGBA_INTEGER || readFormat == eGL_RGB_INTEGER ||
readFormat == eGL_RG_INTEGER || readFormat == eGL_RED_INTEGER) &&
(readType == eGL_BYTE || readType == eGL_SHORT || readType == eGL_INT))
{
readFormat = eGL_RGBA_INTEGER;
readType = eGL_INT;
}
else
{
// TODO maybe fallback to one of the guaranteed ones? or find a way to negotiate a better
// one?
RDCWARN(
"Implementation reported format %s / type %s readback format for %s. Trying with "
"desired format %s / type %s pair anyway.",
ToStr(implFormat).c_str(), ToStr(implType).c_str(), ToStr(origInternalFormat).c_str(),
ToStr(readFormat).c_str(), ToStr(readType).c_str());
}
}
}
PixelUnpackState unpack;
PixelPackState pack;
unpack.Fetch(false);
pack.Fetch(false);
ResetPixelPackState(false, 1);
ResetPixelUnpackState(false, 1);
if(readDirectly)
{
// fast path, we're reading directly in the exact format
memset(dst, 0, dstSliceSize);
GL.glReadPixels(0, 0, width, height, readFormat, readType, (void *)dst);
}
else
{
RDCDEBUG("Readback with remapping for texture format %s", ToStr(origInternalFormat).c_str());
// unfortunately the readback is not in the right format directly, we'll need to read back
// whatever we got and then convert to the output format.
size_t sliceSize = GetByteSize(width, height, 1, readFormat, readType);
byte *readback = new byte[sliceSize];
GL.glReadPixels(0, 0, width, height, readFormat, readType, readback);
uint32_t readCompCount = 1;
if(readFormat == eGL_RGBA || readFormat == eGL_RGBA_INTEGER)
readCompCount = 4;
else if(readFormat == eGL_RGB || readFormat == eGL_RGB_INTEGER)
readCompCount = 3;
else if(readFormat == eGL_RG || readFormat == eGL_RG_INTEGER)
readCompCount = 2;
else if(readFormat == eGL_RED || readFormat == eGL_RED_INTEGER)
readCompCount = 1;
else if(readFormat == eGL_DEPTH_COMPONENT || readFormat == eGL_STENCIL_INDEX)
readCompCount = 1;
else if(readFormat == eGL_DEPTH_STENCIL)
readCompCount = 2;
else
RDCERR("Unexpected implementation format %s, assuming one component",
ToStr(readFormat).c_str());
// how big is a component (1/2/4 bytes)
size_t readCompSize = GetByteSize(1, 1, 1, eGL_RED, readType);
if(depthFormat)
readCompSize = 4;
// if the type didn't change from what the caller expects, we only changed the number of
// components. This is easy to remap
if(type == readType && !disableSRGBCorrect && !depthFormat &&
(origFmt.type == ResourceFormatType::Regular || origFmt.type == ResourceFormatType::A8))
{
RDCDEBUG("Component number changed only");
uint32_t dstCompCount = origFmt.compCount;
byte *srcPixel = readback;
byte *dstPixel = dst;
// for each pixel
for(GLint i = 0; i < width * height; i++)
{
// copy RGB
memcpy(dstPixel, srcPixel, readCompSize * dstCompCount);
// advance dst by RGB
dstPixel += readCompSize * dstCompCount;
// advance src by RGBA
srcPixel += readCompSize * readCompCount;
}
}
else
{
RDCDEBUG("Component format changed");
ResourceFormat readFmt;
readFmt.type = ResourceFormatType::Regular;
readFmt.compCount = readCompCount & 0xff;
readFmt.compByteWidth = readCompSize & 0xff;
if(IsSIntFormat(GetSizedFormat(readFormat)))
{
switch(readType)
{
case eGL_UNSIGNED_INT:
case eGL_UNSIGNED_SHORT:
case eGL_UNSIGNED_BYTE: readFmt.compType = CompType::UInt; break;
case eGL_INT:
case eGL_SHORT:
case eGL_BYTE: readFmt.compType = CompType::SInt; break;
default:
RDCERR("Unexpected readType %s", ToStr(readType).c_str());
readFmt.compType = CompType::UInt;
break;
}
}
else
{
switch(readType)
{
case eGL_UNSIGNED_INT:
case eGL_UNSIGNED_SHORT:
case eGL_UNSIGNED_BYTE: readFmt.compType = CompType::UNorm; break;
case eGL_INT:
case eGL_SHORT:
case eGL_BYTE: readFmt.compType = CompType::SNorm; break;
case eGL_HALF_FLOAT_OES:
case eGL_HALF_FLOAT:
case eGL_FLOAT:
case eGL_DOUBLE: readFmt.compType = CompType::Float; break;
default:
RDCERR("Unexpected readType %s", ToStr(readType).c_str());
readFmt.compType = CompType::UNorm;
break;
}
// if we couldn't enable FRAMEBUFFER_SRGB to ensure the blit is srgb-preserving, we wrote
// out linear data. So we need to under-correct to at least get values approximately right
// even if we lost some information by missing a correct. Since we can't control whether a
// sRGB texture is read as sRGB.
if(!disableSRGBCorrect && origFmt.compType == CompType::UNormSRGB)
readFmt.compType = CompType::UNormSRGB;
}
byte *srcPixel = readback;
byte *dstPixel = dst;
size_t dstStride = origFmt.ElementSize();
bool d24 = false;
// D24 is not written tightly packed, add extra byte for padding
if(origFmt.type == ResourceFormatType::Regular && origFmt.compCount == 1 &&
origFmt.compByteWidth == 3 && origFmt.compType == CompType::Depth)
{
d24 = true;
dstStride = 4;
RDCDEBUG("Handling D24 only");
}
// go pixel-by-pixel, reading in the readback format and writing in the dest format
for(GLint i = 0; i < width * height; i++)
{
FloatVector vec = DecodeFormattedComponents(readFmt, srcPixel);
EncodeFormattedComponents(origFmt, vec, dstPixel);
// GL expects ABGR order for these formats where our standard encoder writes BGRA, swizzle
// here
if(origFmt.type == ResourceFormatType::R4G4B4A4)
{
uint16_t val = 0;
memcpy(&val, dstPixel, sizeof(val));
val = ((val & 0x0fff) << 4) | ((val & 0xf000) >> 12);
memcpy(dstPixel, &val, sizeof(val));
}
else if(origFmt.type == ResourceFormatType::R5G5B5A1)
{
uint16_t val = 0;
memcpy(&val, dstPixel, sizeof(val));
val = ((val & 0x7fff) << 1) | ((val & 0x8000) >> 12);
memcpy(dstPixel, &val, sizeof(val));
}
if(d24)
{
// normally we'd expect D24 to be in the bottom 3 bytes, since D24S8 puts stencil in the
// top byte. However on upload GL doesn't really support a proper preserving upload (or
// not portably) so we specify UNSIGNED_INT. Shifting like this is what a proper GL
// implementation does on read and gives us the right results.
uint32_t *p = (uint32_t *)dstPixel;
*p = (*p << 8) | (*p >> 16);
}
dstPixel += dstStride;
srcPixel += readCompSize * readCompCount;
}
}
delete[] readback;
}
unpack.Apply(false);
pack.Apply(false);
if(swizzleBGRA)
{
// since we read back the texture with RGBA format, we have to flip the R and B components
byte *b = dst;
for(GLint i = 0, n = width * height; i < n; ++i, b += 4)
std::swap(b[0], b[2]);
}
}
if(deltex)
{
GL.glDeleteTextures(1, &deltex);
GL.glBindTexture(target, boundTexture);
}
GL.glDeleteFramebuffers(1, &fbo);
}