diff --git a/renderdoc/core/image_viewer.cpp b/renderdoc/core/image_viewer.cpp index e2190e04f..52f3e147d 100644 --- a/renderdoc/core/image_viewer.cpp +++ b/renderdoc/core/image_viewer.cpp @@ -774,7 +774,7 @@ void ImageViewer::RefreshFile() { // see if we can convert this format on the CPU for proxying bool convertSupported = false; - ConvertComponents(texDetails.format, NULL, &convertSupported); + DecodeFormattedComponents(texDetails.format, NULL, &convertSupported); if(convertSupported) { @@ -810,7 +810,7 @@ void ImageViewer::RefreshFile() { for(uint32_t x = 0; x < mipwidth; x++) { - *dst = ConvertComponents(texDetails.format, src); + *dst = DecodeFormattedComponents(texDetails.format, src); dst++; src += srcStride; } diff --git a/renderdoc/driver/gl/wrappers/gl_emulated.cpp b/renderdoc/driver/gl/wrappers/gl_emulated.cpp index c07d8d211..fbe028261 100644 --- a/renderdoc/driver/gl/wrappers/gl_emulated.cpp +++ b/renderdoc/driver/gl/wrappers/gl_emulated.cpp @@ -31,6 +31,8 @@ #include "../gl_driver.h" #include "../gl_resources.h" #include "driver/shaders/spirv/glslang_compile.h" +#include "maths/formatpacking.h" +#include "maths/half_convert.h" namespace glEmulate { @@ -2583,7 +2585,8 @@ void APIENTRY _glGetTexLevelParameterfv(GLenum target, GLint level, GLenum pname *params = (GLfloat)param; } -void APIENTRY _glGetTexImage(GLenum target, GLint level, GLenum format, GLenum type, void *pixels) +void APIENTRY _glGetTexImage(GLenum target, GLint level, const GLenum format, const GLenum type, + void *pixels) { if((format == eGL_DEPTH_COMPONENT && !HasExt[NV_read_depth]) || (format == eGL_STENCIL && !HasExt[NV_read_stencil]) || @@ -2609,17 +2612,26 @@ void APIENTRY _glGetTexImage(GLenum target, GLint level, GLenum format, GLenum t 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); - GLint fmt = 0; - GL.glGetTexLevelParameteriv(target, level, eGL_TEXTURE_INTERNAL_FORMAT, &fmt); + 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; @@ -2628,53 +2640,158 @@ void APIENTRY _glGetTexImage(GLenum target, GLint level, GLenum format, GLenum t else if(format == eGL_DEPTH_STENCIL) attachment = eGL_DEPTH_STENCIL_ATTACHMENT; - size_t sliceSize = GetByteSize(width, height, 1, format, type); + bool readDirectly = true; - bool fixBGRA = false; - if(!HasExt[EXT_read_format_bgra] && format == eGL_BGRA) + // 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) { - if(type == eGL_UNSIGNED_BYTE) - fixBGRA = true; - else - RDCERR("Can't read back texture without EXT_read_format_bgra extension (data type: %s)", - ToStr(type).c_str()); + readDirectly = false; } - GLuint readtex = boundTexture; - GLuint deltex = 0; - - GLuint fbo = 0; - GL.glGenFramebuffers(1, &fbo); - - PushPopFramebuffer(eGL_FRAMEBUFFER, fbo); - - bool remappedRGB = false; - - // ALPHA can't be bound to an FBO, so we need to blit to R8 by hand. Same with luminance formats. - // Not all devices support RGB framebuffers, so blit that by hand too. - // This should be expanded to handle any format that fails framebuffer compatibility - if(format == eGL_LUMINANCE_ALPHA || format == eGL_LUMINANCE || format == eGL_ALPHA || - (format == eGL_RGB && type != eGL_UNSIGNED_INT_10F_11F_11F_REV)) + // 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) { - RDCDEBUG("Doing manual blit from %s to allow readback", ToStr(format).c_str()); + readDirectly = false; + } - GLenum remapformat = eGL_RED; - GLenum internalformat = eGL_R8; - if(format == eGL_LUMINANCE_ALPHA) + // if we can't attach the texture to a framebuffer, we can't readpixels it directly + if(readDirectly) + { + switch(target) { - remapformat = eGL_RG; - internalformat = eGL_RG8; + 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; } - else if(format == eGL_RGB) + + 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) { - remapformat = eGL_RGBA; - internalformat = eGL_RGBA8; + 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; - if(fmt == eGL_SRGB8) - internalformat = eGL_SRGB8_ALPHA8; - - remappedRGB = true; + // 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 + { + // 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. + 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; @@ -2692,8 +2809,7 @@ void APIENTRY _glGetTexImage(GLenum target, GLint level, GLenum format, GLenum t // allocate the texture GL.glTexParameteri(target, eGL_TEXTURE_MAX_LEVEL, 0); - GL.glTexImage2D(target, 0, internalformat, width, height, 0, remapformat, eGL_UNSIGNED_BYTE, - NULL); + GL.glTexImage2D(target, 0, internalformat, width, height, 0, remapformat, remaptype, NULL); // render to it GL.glFramebufferTexture2D(eGL_FRAMEBUFFER, eGL_COLOR_ATTACHMENT0, target, readtex, 0); @@ -2701,7 +2817,7 @@ void APIENTRY _glGetTexImage(GLenum target, GLint level, GLenum format, GLenum t GLenum fbostatus = GL.glCheckFramebufferStatus(eGL_FRAMEBUFFER); if(fbostatus != eGL_FRAMEBUFFER_COMPLETE) - RDCERR("glReadPixels emulation FBO is %s", ToStr(fbostatus).c_str()); + RDCERR("glReadPixels emulation blit FBO is %s", ToStr(fbostatus).c_str()); // push rendering state GLPushPopState textState; @@ -2716,6 +2832,17 @@ void APIENTRY _glGetTexImage(GLenum target, GLint level, GLenum format, GLenum t 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.glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); GL.glActiveTexture(eGL_TEXTURE0); @@ -2757,7 +2884,7 @@ void APIENTRY _glGetTexImage(GLenum target, GLint level, GLenum format, GLenum t swizzle[2] = 'a'; swizzle[3] = 'a'; } - else if(format == eGL_RGB) + else { swizzle[0] = 'r'; swizzle[1] = 'g'; @@ -2845,6 +2972,9 @@ void APIENTRY _glGetTexImage(GLenum target, GLint level, GLenum format, GLenum t GL.glDeleteBuffers(1, &vb); GL.glDeleteProgram(prog); + if(HasExt[EXT_framebuffer_sRGB] && !oldFBOsrgb) + GL.glDisable(eGL_FRAMEBUFFER_SRGB); + // pop rendering state textState.Pop(true); @@ -2859,11 +2989,29 @@ void APIENTRY _glGetTexImage(GLenum target, GLint level, GLenum format, GLenum t // read from the blitted texture from level 0, as red GL.glBindTexture(target, readtex); level = 0; - format = remapformat; + readFormat = remapformat; + readType = remaptype; 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) @@ -2894,20 +3042,77 @@ void APIENTRY _glGetTexImage(GLenum target, GLint level, GLenum format, GLenum t if(status != eGL_FRAMEBUFFER_COMPLETE) RDCERR("glReadPixels emulation FBO is %s", ToStr(status).c_str()); - byte *dst = (byte *)pixels + d * sliceSize; - GLenum readFormat = fixBGRA ? eGL_RGBA : format; + byte *dst = (byte *)pixels + d * dstSliceSize; - GLenum implFormat = eGL_NONE, implType = eGL_NONE; + // pick the read format/type - GL.glGetIntegerv(eGL_IMPLEMENTATION_COLOR_READ_FORMAT, (GLint *)&implFormat); - GL.glGetIntegerv(eGL_IMPLEMENTATION_COLOR_READ_TYPE, (GLint *)&implType); + 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; - // spec says this must be supported - bool validReadback = (readFormat == eGL_RGBA && type == eGL_UNSIGNED_BYTE); + GL.glGetIntegerv(eGL_IMPLEMENTATION_COLOR_READ_FORMAT, (GLint *)&implFormat); + GL.glGetIntegerv(eGL_IMPLEMENTATION_COLOR_READ_TYPE, (GLint *)&implType); - // the implementation is allowed to support one other format/type pair, it can vary by texture. - // Normally this will be the 'natural' readback format/type pair that we are trying - validReadback |= (readFormat == implFormat && type == 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(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; @@ -2917,94 +3122,161 @@ void APIENTRY _glGetTexImage(GLenum target, GLint level, GLenum format, GLenum t ResetPixelPackState(false, 1); ResetPixelUnpackState(false, 1); - if(validReadback && !remappedRGB) + if(readDirectly) { - memset(dst, 0, sliceSize); - GL.glReadPixels(0, 0, width, height, readFormat, type, (void *)dst); + // 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 { - // unfortunately the readback is not supported directly, we'll need to fudge it. - RDCDEBUG("Reading as %s/%s but impl supported pair is %s/%s", ToStr(readFormat).c_str(), - ToStr(type).c_str(), ToStr(implFormat).c_str(), ToStr(implType).c_str()); + RDCDEBUG("Readback with remapping for texture format %s", ToStr(origInternalFormat).c_str()); - bool simpleType = true; + // 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. - // we can't handle RGB(A) skipping for mismatched readFormat/implFormat if it's not a regular - // component type. - switch(type) + 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 + 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 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 && + (origFmt.type == ResourceFormatType::Regular || origFmt.type == ResourceFormatType::A8)) { - case eGL_UNSIGNED_INT_10F_11F_11F_REV: - case eGL_UNSIGNED_INT_2_10_10_10_REV: - case eGL_UNSIGNED_BYTE_3_3_2: - case eGL_UNSIGNED_SHORT_4_4_4_4: - case eGL_UNSIGNED_SHORT_5_5_5_1: - case eGL_UNSIGNED_SHORT_5_6_5: - case eGL_UNSIGNED_INT_10_10_10_2: - case eGL_UNSIGNED_INT_5_9_9_9_REV: - case eGL_UNSIGNED_INT_24_8: - case eGL_FLOAT_32_UNSIGNED_INT_24_8_REV: simpleType = false; break; - default: break; - } + RDCDEBUG("Component number changed only"); - if(readFormat == implFormat && (type == eGL_HALF_FLOAT_OES || type == eGL_HALF_FLOAT) && - (implType == eGL_HALF_FLOAT_OES || implType == eGL_HALF_FLOAT)) - { - // if the format itself is supported and we just have a mismatch between eGL_HALF_FLOAT_OES - // and eGL_HALF_FLOAT (different enum values), we can just use the implementation's pair - // as-is. + uint32_t dstCompCount = origFmt.compCount; - GL.glReadPixels(0, 0, width, height, readFormat, implType, (void *)dst); - } - else if(remappedRGB || - (readFormat == eGL_RGB && implFormat == eGL_RGBA && type == implType && simpleType)) - { - // if the only difference is that the implementation wants to read RGBA for an RGB format - // (could be understandable if the native storage is RGBA anyway for RGB textures) then we - // can readback into a temporary buffer and strip the alpha. - - size_t size = GetByteSize(width, height, 1, implFormat, type); - byte *readback = new byte[size]; - GL.glReadPixels(0, 0, width, height, implFormat, implType, readback); - - // how big is a component (1/2/4 bytes) - size_t compSize = GetByteSize(1, 1, 1, eGL_RED, type); - - byte *src = readback; + byte *srcPixel = readback; + byte *dstPixel = dst; // for each pixel for(GLint i = 0; i < width * height; i++) { // copy RGB - memcpy(dst, src, compSize * 3); + memcpy(dstPixel, srcPixel, readCompSize * dstCompCount); // advance dst by RGB - dst += compSize * 3; + dstPixel += readCompSize * dstCompCount; // advance src by RGBA - src += compSize * 4; + srcPixel += readCompSize * readCompCount; } - - delete[] readback; - - fixBGRA = false; } else { - RDCERR("Unhandled readback failure"); - memset(dst, 0, sliceSize); + 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: readFmt.compType = CompType::Float; break; + case eGL_DOUBLE: readFmt.compType = CompType::Double; 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; + + // 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)); + } + + dstPixel += origFmt.ElementSize(); + srcPixel += readCompSize * readCompCount; + } } + + delete[] readback; } unpack.Apply(false); pack.Apply(false); - if(fixBGRA) + 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, *(b + 2)); + std::swap(b[0], b[2]); } } diff --git a/renderdoc/driver/vulkan/vk_pixelhistory.cpp b/renderdoc/driver/vulkan/vk_pixelhistory.cpp index efffdaa99..2fd1fce59 100644 --- a/renderdoc/driver/vulkan/vk_pixelhistory.cpp +++ b/renderdoc/driver/vulkan/vk_pixelhistory.cpp @@ -2989,13 +2989,13 @@ void UpdateTestsFailed(const TestsFailedCallback *tfCb, uint32_t eventId, uint32 void FillInColor(ResourceFormat fmt, const PixelHistoryValue &value, ModificationValue &mod) { - FloatVector v4 = ConvertComponents(fmt, value.color); + FloatVector v4 = DecodeFormattedComponents(fmt, value.color); memcpy(mod.col.floatValue, &v4.x, sizeof(v4)); } float GetDepthValue(VkFormat depthFormat, const PixelHistoryValue &value) { - FloatVector v4 = ConvertComponents(MakeResourceFormat(depthFormat), (byte *)&value.depth); + FloatVector v4 = DecodeFormattedComponents(MakeResourceFormat(depthFormat), (byte *)&value.depth); return v4.x; } diff --git a/renderdoc/driver/vulkan/vk_shaderdebug.cpp b/renderdoc/driver/vulkan/vk_shaderdebug.cpp index c2198d90f..a7c39da7c 100644 --- a/renderdoc/driver/vulkan/vk_shaderdebug.cpp +++ b/renderdoc/driver/vulkan/vk_shaderdebug.cpp @@ -3509,7 +3509,7 @@ ShaderDebugTrace *VulkanReplay::DebugVertex(uint32_t eventId, uint32_t vertid, u } else { - FloatVector decoded = ConvertComponents(fmt, data.data()); + FloatVector decoded = DecodeFormattedComponents(fmt, data.data()); val.f.x = decoded.x; val.f.y = decoded.y; diff --git a/renderdoc/maths/formatpacking.cpp b/renderdoc/maths/formatpacking.cpp index 944afe52a..07c8d9367 100644 --- a/renderdoc/maths/formatpacking.cpp +++ b/renderdoc/maths/formatpacking.cpp @@ -127,6 +127,31 @@ Vec3f ConvertFromR9G9B9E5(uint32_t data) return ret; } +uint32_t ConvertToR9G9B9E5(Vec3f data) +{ + float rgb[3] = {data.x, data.y, data.z}; + + uint32_t encodedPixel = 0; + + int exp = -10; + // we pick the highest exponent, losing bits off the bottom of any value that + // needs a lower one, rather than picking a lower one and having to saturate + // values that need a higher one + for(int channel = 0; channel < 3; channel++) + { + int e = 0; + frexpf(rgb[channel], &e); + exp = std::max(exp, e); + } + + for(int channel = 0; channel < 3; channel++) + encodedPixel |= uint32_t(rgb[channel] * 511.0 / (1 << exp)) << (9 * channel); + + encodedPixel |= (exp + 15) << 27; + + return encodedPixel; +} + Vec3f ConvertFromR11G11B10(uint32_t data) { uint32_t mantissas[3] = { @@ -279,7 +304,7 @@ float ConvertLinearToSRGB(float linear) return 1.055f * powf(linear, 1.0f / 2.4f) - 0.055f; } -FloatVector ConvertComponents(const ResourceFormat &fmt, const byte *data, bool *success) +FloatVector DecodeFormattedComponents(const ResourceFormat &fmt, const byte *data, bool *success) { FloatVector ret(0.0f, 0.0f, 0.0f, 1.0f); @@ -577,6 +602,218 @@ FloatVector ConvertComponents(const ResourceFormat &fmt, const byte *data, bool return ret; } +void EncodeFormattedComponents(const ResourceFormat &fmt, FloatVector v, byte *data, bool *success) +{ + uint64_t dummy = 0; + if(!data) + data = (byte *)&dummy; + + // assume success, we'll set it to false if we hit an error + if(success) + *success = true; + + if(fmt.type == ResourceFormatType::R10G10B10A2) + { + if(fmt.BGRAOrder()) + std::swap(v.x, v.z); + uint32_t u; + if(fmt.compType == CompType::SNorm) + u = ConvertToR10G10B10A2SNorm(v); + else if(fmt.compType == CompType::UInt) + u = ConvertToR10G10B10A2(v); + else + u = ConvertToR10G10B10A2(v); + memcpy(data, &u, sizeof(u)); + } + else if(fmt.type == ResourceFormatType::R11G11B10) + { + uint32_t u = ConvertToR11G11B10(Vec3f(v.x, v.y, v.z)); + memcpy(data, &u, sizeof(u)); + } + else if(fmt.type == ResourceFormatType::R5G5B5A1) + { + if(!fmt.BGRAOrder()) + std::swap(v.x, v.z); + + uint16_t u = ConvertToB5G5R5A1(v); + memcpy(data, &u, sizeof(u)); + } + else if(fmt.type == ResourceFormatType::R5G6B5) + { + if(!fmt.BGRAOrder()) + std::swap(v.x, v.z); + + uint16_t u = ConvertToB5G6R5(Vec3f(v.x, v.y, v.z)); + memcpy(data, &u, sizeof(u)); + } + else if(fmt.type == ResourceFormatType::R4G4B4A4) + { + if(!fmt.BGRAOrder()) + std::swap(v.x, v.z); + + uint16_t u = ConvertToB4G4R4A4(v); + memcpy(data, &u, sizeof(u)); + } + else if(fmt.type == ResourceFormatType::R4G4) + { + uint8_t u = ConvertToR4G4(Vec2f(v.x, v.y)); + memcpy(data, &u, sizeof(u)); + } + else if(fmt.type == ResourceFormatType::R9G9B9E5) + { + uint32_t u = ConvertToR9G9B9E5(Vec3f(v.x, v.y, v.z)); + memcpy(data, &u, sizeof(u)); + } + else if(fmt.type == ResourceFormatType::Regular || fmt.type == ResourceFormatType::A8 || + fmt.type == ResourceFormatType::S8) + { + const float *comp = &v.x; + + CompType compType = fmt.compType; + for(size_t c = 0; c < fmt.compCount; c++) + { + // alpha is never interpreted as sRGB + if(compType == CompType::UNormSRGB && c == 3) + compType = CompType::UNorm; + + if(fmt.compByteWidth == 8) + { + // we just upcast + double *d = (double *)data; + uint64_t *u64 = (uint64_t *)data; + int64_t *i64 = (int64_t *)data; + + if(compType == CompType::Double || compType == CompType::Float) + { + *d = *comp; + } + else if(compType == CompType::UInt || compType == CompType::UScaled) + { + *u64 = uint64_t(*comp); + } + else if(compType == CompType::SInt || compType == CompType::SScaled) + { + *i64 = int64_t(*comp); + } + else + { + if(success) + *success = false; + } + } + else if(fmt.compByteWidth == 4) + { + float *f = (float *)data; + uint32_t *u32 = (uint32_t *)data; + int32_t *i32 = (int32_t *)data; + + if(compType == CompType::Float || compType == CompType::Depth) + { + *f = *comp; + } + else if(compType == CompType::UInt || compType == CompType::UScaled) + { + *u32 = uint32_t(RDCCLAMP(*comp, 0.0f, float(UINT32_MAX))); + } + else if(compType == CompType::SInt || compType == CompType::SScaled) + { + *i32 = int32_t(RDCCLAMP(*comp, float(INT32_MIN), float(INT32_MAX))); + } + else + { + if(success) + *success = false; + } + } + else if(fmt.compByteWidth == 2) + { + uint16_t *u16 = (uint16_t *)data; + int16_t *i16 = (int16_t *)data; + + if(compType == CompType::Float) + { + *u16 = ConvertToHalf(*comp); + } + else if(compType == CompType::UInt || compType == CompType::UScaled) + { + *u16 = (uint16_t)RDCCLAMP(*comp, 0.0f, float(UINT16_MAX)); + } + else if(compType == CompType::SInt || compType == CompType::SScaled) + { + *i16 = (int16_t)RDCCLAMP(*comp, float(INT16_MIN), float(INT16_MAX)); + } + // 16-bit depth is UNORM + else if(compType == CompType::UNorm || compType == CompType::Depth) + { + *u16 = uint16_t(RDCCLAMP(*comp, 0.0f, 1.0f) * float(0xffff) + 0.5f); + } + else if(compType == CompType::SNorm) + { + float f = RDCCLAMP(*comp, -1.0f, 1.0f) * 0x7fff; + + if(f < 0.0f) + *i16 = int16_t(f - 0.5f); + else + *i16 = int16_t(f + 0.5f); + } + else + { + if(success) + *success = false; + } + } + else if(fmt.compByteWidth == 1) + { + uint8_t *u8 = (uint8_t *)data; + int8_t *i8 = (int8_t *)data; + + if(compType == CompType::UInt || compType == CompType::UScaled) + { + *u8 = (uint8_t)RDCCLAMP(*comp, 0.0f, float(UINT8_MAX)); + } + else if(compType == CompType::SInt || compType == CompType::SScaled) + { + *i8 = (int8_t)RDCCLAMP(*comp, float(INT8_MIN), float(INT8_MAX)); + } + else if(compType == CompType::UNormSRGB) + { + *u8 = uint8_t(ConvertLinearToSRGB(*comp) * float(0xff) + 0.5f); + } + else if(compType == CompType::UNorm) + { + *u8 = uint8_t(RDCCLAMP(*comp, 0.0f, 1.0f) * float(0xff) + 0.5f); + } + else if(compType == CompType::SNorm) + { + float f = RDCCLAMP(*comp, -1.0f, 1.0f) * 0x7f; + + if(f < 0.0f) + *i8 = int8_t(f - 0.5f); + else + *i8 = int8_t(f + 0.5f); + } + else + { + if(success) + *success = false; + } + } + else + { + RDCERR("Unexpected format to convert from %u %u", fmt.compByteWidth, compType); + } + + comp++; + data += fmt.compByteWidth; + } + } + else + { + if(success) + *success = false; + } +} + #if ENABLED(ENABLE_UNIT_TESTS) #undef None @@ -623,24 +860,25 @@ TEST_CASE("Check ConvertComponents", "[format]") fmt.compType = CompType::UNorm; fmt.compCount = 4; - CHECK(ConvertComponents(fmt, (byte *)&data) == + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(1.0f, 50.0f / 255.0f, 200.0f / 255.0f, 100.0f / 255.0f)); fmt.compCount = 3; - CHECK(ConvertComponents(fmt, (byte *)&data) == + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(1.0f, 50.0f / 255.0f, 200.0f / 255.0f, 1.0f)); fmt.compCount = 2; - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(1.0f, 50.0f / 255.0f, 0.0f, 1.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == + FloatVector(1.0f, 50.0f / 255.0f, 0.0f, 1.0f)); fmt.compCount = 1; - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(1.0f, 0.0f, 0.0f, 1.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(1.0f, 0.0f, 0.0f, 1.0f)); fmt.compCount = 4; fmt.SetBGRAOrder(true); - CHECK(ConvertComponents(fmt, (byte *)&data) == + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(200.0f / 255.0f, 50.0f / 255.0f, 1.0f, 100.0f / 255.0f)); fmt.compCount = 3; fmt.SetBGRAOrder(true); - CHECK(ConvertComponents(fmt, (byte *)&data) == + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(200.0f / 255.0f, 50.0f / 255.0f, 1.0f, 1.0f)); }; @@ -650,23 +888,25 @@ TEST_CASE("Check ConvertComponents", "[format]") fmt.compCount = 4; // alpha should still be 100.0f / 255.0f because alpha is not sRGB corrected - CHECK(ConvertComponents(fmt, (byte *)&data) == + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(1.0f, 0.031896f, 0.577581f, 100.0f / 255.0f)); fmt.compCount = 3; - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(1.0f, 0.031896f, 0.577581f, 1.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == + FloatVector(1.0f, 0.031896f, 0.577581f, 1.0f)); fmt.compCount = 2; - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(1.0f, 0.031896f, 0.0f, 1.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(1.0f, 0.031896f, 0.0f, 1.0f)); fmt.compCount = 1; - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(1.0f, 0.0f, 0.0f, 1.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(1.0f, 0.0f, 0.0f, 1.0f)); fmt.compCount = 4; fmt.SetBGRAOrder(true); - CHECK(ConvertComponents(fmt, (byte *)&data) == + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(0.577581f, 0.031896f, 1.0f, 100.0f / 255.0f)); fmt.compCount = 3; fmt.SetBGRAOrder(true); - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(0.577581f, 0.031896f, 1.0f, 1.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == + FloatVector(0.577581f, 0.031896f, 1.0f, 1.0f)); }; SECTION("UInt") @@ -674,21 +914,23 @@ TEST_CASE("Check ConvertComponents", "[format]") fmt.compType = CompType::UInt; fmt.compCount = 4; - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(255.0f, 50.0f, 200.0f, 100.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == + FloatVector(255.0f, 50.0f, 200.0f, 100.0f)); fmt.compCount = 3; - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(255.0f, 50.0f, 200.0f, 1.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(255.0f, 50.0f, 200.0f, 1.0f)); fmt.compCount = 2; - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(255.0f, 50.0f, 0.0f, 1.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(255.0f, 50.0f, 0.0f, 1.0f)); fmt.compCount = 1; - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(255.0f, 0.0f, 0.0f, 1.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(255.0f, 0.0f, 0.0f, 1.0f)); fmt.compCount = 4; fmt.SetBGRAOrder(true); - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(200.0f, 50.0f, 255.0f, 100.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == + FloatVector(200.0f, 50.0f, 255.0f, 100.0f)); fmt.compCount = 3; fmt.SetBGRAOrder(true); - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(200.0f, 50.0f, 255.0f, 1.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(200.0f, 50.0f, 255.0f, 1.0f)); }; data.i8[0] = 127; @@ -701,21 +943,25 @@ TEST_CASE("Check ConvertComponents", "[format]") fmt.compType = CompType::SInt; fmt.compCount = 4; - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(127.0f, 50.0f, -128.0f, 100.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == + FloatVector(127.0f, 50.0f, -128.0f, 100.0f)); fmt.compCount = 3; - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(127.0f, 50.0f, -128.0f, 1.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == + FloatVector(127.0f, 50.0f, -128.0f, 1.0f)); fmt.compCount = 2; - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(127.0f, 50.0f, 0.0f, 1.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(127.0f, 50.0f, 0.0f, 1.0f)); fmt.compCount = 1; - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(127.0f, 0.0f, 0.0f, 1.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(127.0f, 0.0f, 0.0f, 1.0f)); fmt.compCount = 4; fmt.SetBGRAOrder(true); - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(-128.0f, 50.0f, 127.0f, 100.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == + FloatVector(-128.0f, 50.0f, 127.0f, 100.0f)); fmt.compCount = 3; fmt.SetBGRAOrder(true); - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(-128.0f, 50.0f, 127.0f, 1.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == + FloatVector(-128.0f, 50.0f, 127.0f, 1.0f)); }; SECTION("SNorm") @@ -723,23 +969,26 @@ TEST_CASE("Check ConvertComponents", "[format]") fmt.compType = CompType::SNorm; fmt.compCount = 4; - CHECK(ConvertComponents(fmt, (byte *)&data) == + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(1.0f, 50.0f / 127.0f, -1.0f, 100.0f / 127.0f)); fmt.compCount = 3; - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(1.0f, 50.0f / 127.0f, -1.0f, 1.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == + FloatVector(1.0f, 50.0f / 127.0f, -1.0f, 1.0f)); fmt.compCount = 2; - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(1.0f, 50.0f / 127.0f, 0.0f, 1.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == + FloatVector(1.0f, 50.0f / 127.0f, 0.0f, 1.0f)); fmt.compCount = 1; - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(1.0f, 0.0f, 0.0f, 1.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(1.0f, 0.0f, 0.0f, 1.0f)); fmt.compCount = 4; fmt.SetBGRAOrder(true); - CHECK(ConvertComponents(fmt, (byte *)&data) == + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(-1.0f, 50.0f / 127.0f, 1.0f, 100.0f / 127.0f)); fmt.compCount = 3; fmt.SetBGRAOrder(true); - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(-1.0f, 50.0f / 127.0f, 1.0f, 1.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == + FloatVector(-1.0f, 50.0f / 127.0f, 1.0f, 1.0f)); }; }; @@ -757,21 +1006,21 @@ TEST_CASE("Check ConvertComponents", "[format]") fmt.compType = CompType::Float; fmt.compCount = 4; - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(1.0f, 1250.0f, 50.0f, 3.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(1.0f, 1250.0f, 50.0f, 3.0f)); fmt.compCount = 3; - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(1.0f, 1250.0f, 50.0f, 1.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(1.0f, 1250.0f, 50.0f, 1.0f)); fmt.compCount = 2; - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(1.0f, 1250.0f, 0.0f, 1.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(1.0f, 1250.0f, 0.0f, 1.0f)); fmt.compCount = 1; - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(1.0f, 0.0f, 0.0f, 1.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(1.0f, 0.0f, 0.0f, 1.0f)); fmt.compCount = 4; fmt.SetBGRAOrder(true); - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(50.0f, 1250.0f, 1.0f, 3.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(50.0f, 1250.0f, 1.0f, 3.0f)); fmt.compCount = 3; fmt.SetBGRAOrder(true); - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(50.0f, 1250.0f, 1.0f, 1.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(50.0f, 1250.0f, 1.0f, 1.0f)); }; data.u16[0] = 65535; @@ -784,25 +1033,25 @@ TEST_CASE("Check ConvertComponents", "[format]") fmt.compType = CompType::UNorm; fmt.compCount = 4; - CHECK(ConvertComponents(fmt, (byte *)&data) == + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(1.0f, 1250.0f / 65535.0f, 5273.0f / 65535.0f, 101.0f / 65535.0f)); fmt.compCount = 3; - CHECK(ConvertComponents(fmt, (byte *)&data) == + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(1.0f, 1250.0f / 65535.0f, 5273.0f / 65535.0f, 1.0f)); fmt.compCount = 2; - CHECK(ConvertComponents(fmt, (byte *)&data) == + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(1.0f, 1250.0f / 65535.0f, 0.0f, 1.0f)); fmt.compCount = 1; - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(1.0f, 0.0f, 0.0f, 1.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(1.0f, 0.0f, 0.0f, 1.0f)); fmt.compCount = 4; fmt.SetBGRAOrder(true); - CHECK(ConvertComponents(fmt, (byte *)&data) == + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(5273.0f / 65535.0f, 1250.0f / 65535.0f, 1.0f, 101.0f / 65535.0f)); fmt.compCount = 3; fmt.SetBGRAOrder(true); - CHECK(ConvertComponents(fmt, (byte *)&data) == + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(5273.0f / 65535.0f, 1250.0f / 65535.0f, 1.0f, 1.0f)); }; @@ -811,21 +1060,26 @@ TEST_CASE("Check ConvertComponents", "[format]") fmt.compType = CompType::UInt; fmt.compCount = 4; - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(65535.0f, 1250.0f, 5273.0f, 101.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == + FloatVector(65535.0f, 1250.0f, 5273.0f, 101.0f)); fmt.compCount = 3; - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(65535.0f, 1250.0f, 5273.0f, 1.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == + FloatVector(65535.0f, 1250.0f, 5273.0f, 1.0f)); fmt.compCount = 2; - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(65535.0f, 1250.0f, 0.0f, 1.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == + FloatVector(65535.0f, 1250.0f, 0.0f, 1.0f)); fmt.compCount = 1; - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(65535.0f, 0.0f, 0.0f, 1.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(65535.0f, 0.0f, 0.0f, 1.0f)); fmt.compCount = 4; fmt.SetBGRAOrder(true); - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(5273.0f, 1250.0f, 65535.0f, 101.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == + FloatVector(5273.0f, 1250.0f, 65535.0f, 101.0f)); fmt.compCount = 3; fmt.SetBGRAOrder(true); - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(5273.0f, 1250.0f, 65535.0f, 1.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == + FloatVector(5273.0f, 1250.0f, 65535.0f, 1.0f)); }; data.i16[0] = 32767; @@ -838,23 +1092,26 @@ TEST_CASE("Check ConvertComponents", "[format]") fmt.compType = CompType::SInt; fmt.compCount = 4; - CHECK(ConvertComponents(fmt, (byte *)&data) == + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(32767.0f, 1250.0f, -32768.0f, 101.0f)); fmt.compCount = 3; - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(32767.0f, 1250.0f, -32768.0f, 1.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == + FloatVector(32767.0f, 1250.0f, -32768.0f, 1.0f)); fmt.compCount = 2; - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(32767.0f, 1250.0f, 0.0f, 1.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == + FloatVector(32767.0f, 1250.0f, 0.0f, 1.0f)); fmt.compCount = 1; - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(32767.0f, 0.0f, 0.0f, 1.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(32767.0f, 0.0f, 0.0f, 1.0f)); fmt.compCount = 4; fmt.SetBGRAOrder(true); - CHECK(ConvertComponents(fmt, (byte *)&data) == + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(-32768.0f, 1250.0f, 32767.0f, 101.0f)); fmt.compCount = 3; fmt.SetBGRAOrder(true); - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(-32768.0f, 1250.0f, 32767.0f, 1.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == + FloatVector(-32768.0f, 1250.0f, 32767.0f, 1.0f)); }; SECTION("SNorm") @@ -862,25 +1119,25 @@ TEST_CASE("Check ConvertComponents", "[format]") fmt.compType = CompType::SNorm; fmt.compCount = 4; - CHECK(ConvertComponents(fmt, (byte *)&data) == + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(1.0f, 1250.0f / 32767.0f, -1.0f, 101.0f / 32767.0f)); fmt.compCount = 3; - CHECK(ConvertComponents(fmt, (byte *)&data) == + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(1.0f, 1250.0f / 32767.0f, -1.0f, 1.0f)); fmt.compCount = 2; - CHECK(ConvertComponents(fmt, (byte *)&data) == + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(1.0f, 1250.0f / 32767.0f, 0.0f, 1.0f)); fmt.compCount = 1; - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(1.0f, 0.0f, 0.0f, 1.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(1.0f, 0.0f, 0.0f, 1.0f)); fmt.compCount = 4; fmt.SetBGRAOrder(true); - CHECK(ConvertComponents(fmt, (byte *)&data) == + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(-1.0f, 1250.0f / 32767.0f, 1.0f, 101.0f / 32767.0f)); fmt.compCount = 3; fmt.SetBGRAOrder(true); - CHECK(ConvertComponents(fmt, (byte *)&data) == + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(-1.0f, 1250.0f / 32767.0f, 1.0f, 1.0f)); }; }; @@ -899,21 +1156,21 @@ TEST_CASE("Check ConvertComponents", "[format]") fmt.compType = CompType::Float; fmt.compCount = 4; - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(1.0f, 1250.0f, 50.0f, 3.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(1.0f, 1250.0f, 50.0f, 3.0f)); fmt.compCount = 3; - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(1.0f, 1250.0f, 50.0f, 1.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(1.0f, 1250.0f, 50.0f, 1.0f)); fmt.compCount = 2; - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(1.0f, 1250.0f, 0.0f, 1.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(1.0f, 1250.0f, 0.0f, 1.0f)); fmt.compCount = 1; - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(1.0f, 0.0f, 0.0f, 1.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(1.0f, 0.0f, 0.0f, 1.0f)); fmt.compCount = 4; fmt.SetBGRAOrder(true); - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(50.0f, 1250.0f, 1.0f, 3.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(50.0f, 1250.0f, 1.0f, 3.0f)); fmt.compCount = 3; fmt.SetBGRAOrder(true); - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(50.0f, 1250.0f, 1.0f, 1.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(50.0f, 1250.0f, 1.0f, 1.0f)); }; data.u32[0] = 655350; @@ -926,23 +1183,26 @@ TEST_CASE("Check ConvertComponents", "[format]") fmt.compType = CompType::UInt; fmt.compCount = 4; - CHECK(ConvertComponents(fmt, (byte *)&data) == + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(655350.0f, 12500.0f, 52730.0f, 1010.0f)); fmt.compCount = 3; - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(655350.0f, 12500.0f, 52730.0f, 1.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == + FloatVector(655350.0f, 12500.0f, 52730.0f, 1.0f)); fmt.compCount = 2; - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(655350.0f, 12500.0f, 0.0f, 1.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == + FloatVector(655350.0f, 12500.0f, 0.0f, 1.0f)); fmt.compCount = 1; - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(655350.0f, 0.0f, 0.0f, 1.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(655350.0f, 0.0f, 0.0f, 1.0f)); fmt.compCount = 4; fmt.SetBGRAOrder(true); - CHECK(ConvertComponents(fmt, (byte *)&data) == + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(52730.0f, 12500.0f, 655350.0f, 1010.0f)); fmt.compCount = 3; fmt.SetBGRAOrder(true); - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(52730.0f, 12500.0f, 655350.0f, 1.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == + FloatVector(52730.0f, 12500.0f, 655350.0f, 1.0f)); }; data.i32[0] = 327670; @@ -955,24 +1215,26 @@ TEST_CASE("Check ConvertComponents", "[format]") fmt.compType = CompType::SInt; fmt.compCount = 4; - CHECK(ConvertComponents(fmt, (byte *)&data) == + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(327670.0f, 12500.0f, -327680.0f, 1010.0f)); fmt.compCount = 3; - CHECK(ConvertComponents(fmt, (byte *)&data) == + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(327670.0f, 12500.0f, -327680.0f, 1.0f)); fmt.compCount = 2; - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(327670.0f, 12500.0f, 00.0f, 1.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == + FloatVector(327670.0f, 12500.0f, 00.0f, 1.0f)); fmt.compCount = 1; - CHECK(ConvertComponents(fmt, (byte *)&data) == FloatVector(327670.0f, 00.0f, 00.0f, 1.0f)); + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == + FloatVector(327670.0f, 00.0f, 00.0f, 1.0f)); fmt.compCount = 4; fmt.SetBGRAOrder(true); - CHECK(ConvertComponents(fmt, (byte *)&data) == + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(-327680.0f, 12500.0f, 327670.0f, 1010.0f)); fmt.compCount = 3; fmt.SetBGRAOrder(true); - CHECK(ConvertComponents(fmt, (byte *)&data) == + CHECK(DecodeFormattedComponents(fmt, (byte *)&data) == FloatVector(-327680.0f, 12500.0f, 327670.0f, 1.0f)); }; }; diff --git a/renderdoc/maths/formatpacking.h b/renderdoc/maths/formatpacking.h index fd344ec8c..9bc00a0e4 100644 --- a/renderdoc/maths/formatpacking.h +++ b/renderdoc/maths/formatpacking.h @@ -80,6 +80,27 @@ inline uint32_t ConvertToR10G10B10A2(Vec4f data) (uint32_t(w * 3) << 30); } +inline uint32_t ConvertToR10G10B10A2(Vec4u data) +{ + return (uint32_t(data.x & 0x3ff) << 0) | (uint32_t(data.y & 0x3ff) << 10) | + (uint32_t(data.z & 0x3ff) << 20) | (uint32_t(data.w & 0x3) << 30); +} + +inline uint32_t ConvertToR10G10B10A2SNorm(Vec4f data) +{ + float x = data.x < 1.0f ? (data.x > -1.0f ? data.x : -1.0f) : 1.0f; + float y = data.y < 1.0f ? (data.y > -1.0f ? data.y : -1.0f) : 1.0f; + float z = data.z < 1.0f ? (data.z > -1.0f ? data.z : -1.0f) : 1.0f; + float w = data.w < 1.0f ? (data.w > -1.0f ? data.w : -1.0f) : 1.0f; + + uint32_t xu = x >= 0.0f ? uint32_t(x * 511 + 0.5f) : (1024U + uint32_t(-x * 511 + 0.5f)); + uint32_t yu = y >= 0.0f ? uint32_t(y * 511 + 0.5f) : (1024U + uint32_t(-y * 511 + 0.5f)); + uint32_t zu = z >= 0.0f ? uint32_t(z * 511 + 0.5f) : (1024U + uint32_t(-z * 511 + 0.5f)); + uint32_t wu = w >= 0.0f ? uint32_t(w * 1) : (2U + uint32_t(-z * 1)); + + return (uint32_t(xu) << 0) | (uint32_t(yu) << 10) | (uint32_t(zu) << 20) | (uint32_t(wu) << 30); +} + Vec3f ConvertFromR11G11B10(uint32_t data); uint32_t ConvertToR11G11B10(Vec3f data); @@ -89,24 +110,65 @@ inline Vec4f ConvertFromB5G5R5A1(uint16_t data) (float)((data >> 0) & 0x1f) / 31.0f, ((data & 0x8000) > 0) ? 1.0f : 0.0f); } +inline uint16_t ConvertToB5G5R5A1(Vec4f data) +{ + float x = data.x < 1.0f ? (data.x > 0.0f ? data.x : 0.0f) : 1.0f; + float y = data.y < 1.0f ? (data.y > 0.0f ? data.y : 0.0f) : 1.0f; + float z = data.z < 1.0f ? (data.z > 0.0f ? data.z : 0.0f) : 1.0f; + float w = data.w < 1.0f ? (data.w > 0.0f ? data.w : 0.0f) : 1.0f; + + return (uint16_t(x * 0x1f + 0.5f) << 10) | (uint16_t(y * 0x1f + 0.5f) << 5) | + (uint16_t(z * 0x1f + 0.5f) << 0) | (uint16_t(w * 0x1 + 0.5f) << 15); +} + inline Vec3f ConvertFromB5G6R5(uint16_t data) { return Vec3f((float)((data >> 11) & 0x1f) / 31.0f, (float)((data >> 5) & 0x3f) / 63.0f, (float)((data >> 0) & 0x1f) / 31.0f); } +inline uint16_t ConvertToB5G6R5(Vec3f data) +{ + float x = data.x < 1.0f ? (data.x > 0.0f ? data.x : 0.0f) : 1.0f; + float y = data.y < 1.0f ? (data.y > 0.0f ? data.y : 0.0f) : 1.0f; + float z = data.z < 1.0f ? (data.z > 0.0f ? data.z : 0.0f) : 1.0f; + + return (uint16_t(x * 0x1f + 0.5f) << 11) | (uint16_t(y * 0x3f + 0.5f) << 5) | + (uint16_t(z * 0x1f + 0.5f) << 0); +} + inline Vec4f ConvertFromB4G4R4A4(uint16_t data) { return Vec4f((float)((data >> 8) & 0xf) / 15.0f, (float)((data >> 4) & 0xf) / 15.0f, (float)((data >> 0) & 0xf) / 15.0f, (float)((data >> 12) & 0xf) / 15.0f); } +inline uint16_t ConvertToB4G4R4A4(Vec4f data) +{ + float x = data.x < 1.0f ? (data.x > 0.0f ? data.x : 0.0f) : 1.0f; + float y = data.y < 1.0f ? (data.y > 0.0f ? data.y : 0.0f) : 1.0f; + float z = data.z < 1.0f ? (data.z > 0.0f ? data.z : 0.0f) : 1.0f; + float w = data.w < 1.0f ? (data.w > 0.0f ? data.w : 0.0f) : 1.0f; + + return (uint16_t(x * 0xf + 0.5f) << 8) | (uint16_t(y * 0xf + 0.5f) << 4) | + (uint16_t(z * 0xf + 0.5f) << 0) | (uint16_t(w * 0xf + 0.5f) << 12); +} + inline Vec4f ConvertFromR4G4(uint8_t data) { return Vec4f((float)((data >> 0) & 0xf) / 15.0f, (float)((data >> 4) & 0xf) / 15.0f, 0.0f, 0.0f); } +inline uint8_t ConvertToR4G4(Vec2f data) +{ + float x = data.x < 1.0f ? (data.x > 0.0f ? data.x : 0.0f) : 1.0f; + float y = data.y < 1.0f ? (data.y > 0.0f ? data.y : 0.0f) : 1.0f; + + return (uint8_t(x * 0xf + 0.5f) << 0) | (uint8_t(y * 0xf + 0.5f) << 4); +} + Vec3f ConvertFromR9G9B9E5(uint32_t data); +uint32_t ConvertToR9G9B9E5(Vec3f data); float ConvertFromSRGB8(uint8_t comp); float ConvertSRGBToLinear(float srgbF); @@ -116,4 +178,7 @@ float ConvertLinearToSRGB(float linear); typedef uint8_t byte; struct ResourceFormat; -FloatVector ConvertComponents(const ResourceFormat &fmt, const byte *data, bool *success = NULL); +FloatVector DecodeFormattedComponents(const ResourceFormat &fmt, const byte *data, + bool *success = NULL); +void EncodeFormattedComponents(const ResourceFormat &fmt, FloatVector v, byte *data, + bool *success = NULL); diff --git a/renderdoc/os/win32/win32_shellext.cpp b/renderdoc/os/win32/win32_shellext.cpp index 0bb8c5140..7848ffa00 100644 --- a/renderdoc/os/win32/win32_shellext.cpp +++ b/renderdoc/os/win32/win32_shellext.cpp @@ -613,7 +613,7 @@ struct RDCThumbnailProvider : public IThumbnailProvider, IInitializeWithStream else if(resourceType == ResourceFormatType::D16S8) rgba.x = rgba.y = rgba.z = float(*(uint16_t *)src) / 65535.0f; else - rgba = ConvertComponents(m_ddsData.format, src); + rgba = DecodeFormattedComponents(m_ddsData.format, src); if(resourceType == ResourceFormatType::A8) rgba.y = rgba.z = rgba.x; diff --git a/renderdoc/replay/replay_controller.cpp b/renderdoc/replay/replay_controller.cpp index 059d01c2f..2b440e8ce 100644 --- a/renderdoc/replay/replay_controller.cpp +++ b/renderdoc/replay/replay_controller.cpp @@ -1279,7 +1279,7 @@ bool ReplayController::SaveTexture(const TextureSave &saveData, const char *path { for(uint32_t x = 0; x < td.width; x++) { - FloatVector pixel = ConvertComponents(saveFmt, srcData); + FloatVector pixel = DecodeFormattedComponents(saveFmt, srcData); srcData += pixStride; // HDR can't represent negative values diff --git a/renderdoc/replay/replay_driver.cpp b/renderdoc/replay/replay_driver.cpp index 5d6c33d5b..7ccb9113c 100644 --- a/renderdoc/replay/replay_driver.cpp +++ b/renderdoc/replay/replay_driver.cpp @@ -624,7 +624,7 @@ FloatVector HighlightCache::InterpretVertex(const byte *data, uint32_t vert, return FloatVector(0.0f, 0.0f, 0.0f, 1.0f); } - return ConvertComponents(fmt, data); + return DecodeFormattedComponents(fmt, data); } uint64_t inthash(uint64_t val, uint64_t seed)