From 7c663de948f5cdd929e82976ce22a6830e9626f3 Mon Sep 17 00:00:00 2001 From: baldurk Date: Sun, 14 Dec 2014 11:17:02 +0000 Subject: [PATCH] Add vendor check to avoid glCopyImageSubData on <4x4 compressed mips --- renderdoc/driver/gl/gl_common.cpp | 35 ++++ renderdoc/driver/gl/gl_common.h | 1 + renderdoc/driver/gl/gl_manager.cpp | 266 +++++++++++++++++++++++++++-- 3 files changed, 286 insertions(+), 16 deletions(-) diff --git a/renderdoc/driver/gl/gl_common.cpp b/renderdoc/driver/gl/gl_common.cpp index 46bda1f4e..2fd3c3cde 100644 --- a/renderdoc/driver/gl/gl_common.cpp +++ b/renderdoc/driver/gl/gl_common.cpp @@ -180,6 +180,41 @@ void DoVendorChecks(const GLHookSet &gl, GLWindowingData context) } } + // AMD throws an error if we try to copy the mips that are smaller than 4x4, + if(gl.glGetError && gl.glGenTextures && gl.glBindTexture && gl.glCopyImageSubData && + gl.glTexStorage2D && gl.glTexParameteri && gl.glDeleteTextures) + { + GLuint texs[2]; + gl.glGenTextures(2, texs); + + gl.glBindTexture(eGL_TEXTURE_2D, texs[0]); + gl.glTexStorage2D(eGL_TEXTURE_2D, 1, eGL_COMPRESSED_RGBA_S3TC_DXT1_EXT, 1, 1); + gl.glTexParameteri(eGL_TEXTURE_2D, eGL_TEXTURE_MAX_LEVEL, 1); + + gl.glBindTexture(eGL_TEXTURE_2D, texs[1]); + gl.glTexStorage2D(eGL_TEXTURE_2D, 1, eGL_COMPRESSED_RGBA_S3TC_DXT1_EXT, 1, 1); + gl.glTexParameteri(eGL_TEXTURE_2D, eGL_TEXTURE_MAX_LEVEL, 1); + + // clear all error flags. + GLenum err = gl.glGetError(); + while(err != eGL_NONE) err = gl.glGetError(); + + gl.glCopyImageSubData(texs[0], eGL_TEXTURE_2D, 0, 0, 0, 0, texs[1], eGL_TEXTURE_2D, 0, 0, 0, 0, 1, 1, 1); + + err = gl.glGetError(); + + if(err != eGL_NONE) + { + // if we got an error trying to query that, we should enable this hack + VendorCheck[VendorCheck_AMD_copy_compressed_tinymips] = true; + + RDCWARN("Using hack to avoid glCopyImageSubData on lowest mips of compressed texture"); + } + + gl.glBindTexture(eGL_TEXTURE_2D, 0); + gl.glDeleteTextures(2, texs); + } + // only do this when we have a proper context e.g. on windows where an old // context is first created. Check to see if FBOs or VAOs are shared between // contexts. diff --git a/renderdoc/driver/gl/gl_common.h b/renderdoc/driver/gl/gl_common.h index ffbabb786..805b26874 100644 --- a/renderdoc/driver/gl/gl_common.h +++ b/renderdoc/driver/gl/gl_common.h @@ -111,6 +111,7 @@ enum VendorCheckEnum VendorCheck_EXT_fbo_shared, VendorCheck_EXT_vao_shared, VendorCheck_AMD_polygon_mode_query, + VendorCheck_AMD_copy_compressed_tinymips, VendorCheck_Count, }; extern bool VendorCheck[VendorCheck_Count]; diff --git a/renderdoc/driver/gl/gl_manager.cpp b/renderdoc/driver/gl/gl_manager.cpp index 3a6104503..b61ee3f67 100644 --- a/renderdoc/driver/gl/gl_manager.cpp +++ b/renderdoc/driver/gl/gl_manager.cpp @@ -272,6 +272,49 @@ bool GLResourceManager::Prepare_InitialState(GLResource res) int maxlevel = mips-1; gl.glTextureParameterivEXT(res.name, details.curType, eGL_TEXTURE_MAX_LEVEL, (GLint *)&maxlevel); + bool iscomp = IsCompressedFormat(details.internalFormat); + + GLint packParams[8]; + GLint unpackParams[8]; + if(iscomp && VendorCheck[VendorCheck_AMD_copy_compressed_tinymips]) + { + gl.glGetIntegerv(eGL_PACK_SWAP_BYTES, &packParams[0]); + gl.glGetIntegerv(eGL_PACK_LSB_FIRST, &packParams[1]); + gl.glGetIntegerv(eGL_PACK_ROW_LENGTH, &packParams[2]); + gl.glGetIntegerv(eGL_PACK_IMAGE_HEIGHT, &packParams[3]); + gl.glGetIntegerv(eGL_PACK_SKIP_PIXELS, &packParams[4]); + gl.glGetIntegerv(eGL_PACK_SKIP_ROWS, &packParams[5]); + gl.glGetIntegerv(eGL_PACK_SKIP_IMAGES, &packParams[6]); + gl.glGetIntegerv(eGL_PACK_ALIGNMENT, &packParams[7]); + + gl.glPixelStorei(eGL_PACK_SWAP_BYTES, 0); + gl.glPixelStorei(eGL_PACK_LSB_FIRST, 0); + gl.glPixelStorei(eGL_PACK_ROW_LENGTH, 0); + gl.glPixelStorei(eGL_PACK_IMAGE_HEIGHT, 0); + gl.glPixelStorei(eGL_PACK_SKIP_PIXELS, 0); + gl.glPixelStorei(eGL_PACK_SKIP_ROWS, 0); + gl.glPixelStorei(eGL_PACK_SKIP_IMAGES, 0); + gl.glPixelStorei(eGL_PACK_ALIGNMENT, 1); + + gl.glGetIntegerv(eGL_UNPACK_SWAP_BYTES, &unpackParams[0]); + gl.glGetIntegerv(eGL_UNPACK_LSB_FIRST, &unpackParams[1]); + gl.glGetIntegerv(eGL_UNPACK_ROW_LENGTH, &unpackParams[2]); + gl.glGetIntegerv(eGL_UNPACK_IMAGE_HEIGHT, &unpackParams[3]); + gl.glGetIntegerv(eGL_UNPACK_SKIP_PIXELS, &unpackParams[4]); + gl.glGetIntegerv(eGL_UNPACK_SKIP_ROWS, &unpackParams[5]); + gl.glGetIntegerv(eGL_UNPACK_SKIP_IMAGES, &unpackParams[6]); + gl.glGetIntegerv(eGL_UNPACK_ALIGNMENT, &unpackParams[7]); + + gl.glPixelStorei(eGL_UNPACK_SWAP_BYTES, 0); + gl.glPixelStorei(eGL_UNPACK_LSB_FIRST, 0); + gl.glPixelStorei(eGL_UNPACK_ROW_LENGTH, 0); + gl.glPixelStorei(eGL_UNPACK_IMAGE_HEIGHT, 0); + gl.glPixelStorei(eGL_UNPACK_SKIP_PIXELS, 0); + gl.glPixelStorei(eGL_UNPACK_SKIP_ROWS, 0); + gl.glPixelStorei(eGL_UNPACK_SKIP_IMAGES, 0); + gl.glPixelStorei(eGL_UNPACK_ALIGNMENT, 1); + } + // copy over mips for(int i=0; i < mips; i++) { @@ -286,15 +329,89 @@ bool GLResourceManager::Prepare_InitialState(GLResource res) details.curType == eGL_TEXTURE_2D_ARRAY) d = details.depth; - // it seems like everything explodes if I do glCopyImageSubData on a D32F_S8 texture - in-program the overlay - // gets corrupted as one UBO seems to not provide data anymore until it's "refreshed". It seems like a driver bug, - // nvidia specific. - // In most cases a program isn't going to rely on the contents of a depth-stencil buffer (shadow maps that it might - // require would be depth-only formatted). - if(details.internalFormat == eGL_DEPTH32F_STENCIL8 && VendorCheck[VendorCheck_NV_avoid_D32S8_copy]) - RDCDEBUG("Not fetching initial contents of D32F_S8 texture"); + // AMD throws an error copying mips that are smaller than the block size in one dimension, so do copy via + // CPU instead (will be slow, potentially we could optimise this if there's a different GPU-side image copy + // routine that works on these dimensions. Hopefully there'll only be a couple of such mips). + if(iscomp && VendorCheck[VendorCheck_AMD_copy_compressed_tinymips] && (w < 4 || h < 4)) + { + 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(details.curType != eGL_TEXTURE_CUBE_MAP) + { + targets[0] = details.curType; + count = 1; + } + + for(int trg=0; trg < count; trg++) + { + GLint compSize; + gl.glGetTextureLevelParameterivEXT(res.name, targets[trg], i, eGL_TEXTURE_COMPRESSED_IMAGE_SIZE, &compSize); + + size_t size = compSize; + + // sometimes cubemaps return the compressed image size for the whole texture, but we read it + // face by face + if(VendorCheck[VendorCheck_EXT_compressed_cube_size] && details.curType == eGL_TEXTURE_CUBE_MAP) + size /= 6; + + byte *buf = new byte[size]; + + // read to CPU + gl.glGetCompressedTextureImageEXT(res.name, targets[trg], i, buf); + + // write to GPU + if(details.dimension == 1) + gl.glCompressedTextureSubImage1DEXT(tex, targets[trg], i, 0, w, details.internalFormat, (GLsizei)size, buf); + else if(details.dimension == 2) + gl.glCompressedTextureSubImage2DEXT(tex, targets[trg], i, 0, 0, w, h, details.internalFormat, (GLsizei)size, buf); + else if(details.dimension == 3) + gl.glCompressedTextureSubImage3DEXT(tex, targets[trg], i, 0, 0, 0, w, h, d, details.internalFormat, (GLsizei)size, buf); + + delete[] buf; + } + } else - gl.glCopyImageSubData(res.name, details.curType, i, 0, 0, 0, tex, details.curType, i, 0, 0, 0, w, h, d); + { + // it seems like everything explodes if I do glCopyImageSubData on a D32F_S8 texture - in-program the overlay + // gets corrupted as one UBO seems to not provide data anymore until it's "refreshed". It seems like a driver bug, + // nvidia specific. + // In most cases a program isn't going to rely on the contents of a depth-stencil buffer (shadow maps that it might + // require would be depth-only formatted). + if(details.internalFormat == eGL_DEPTH32F_STENCIL8 && VendorCheck[VendorCheck_NV_avoid_D32S8_copy]) + RDCDEBUG("Not fetching initial contents of D32F_S8 texture"); + else + gl.glCopyImageSubData(res.name, details.curType, i, 0, 0, 0, tex, details.curType, i, 0, 0, 0, w, h, d); + } + } + + if(iscomp && VendorCheck[VendorCheck_AMD_copy_compressed_tinymips]) + { + gl.glPixelStorei(eGL_PACK_SWAP_BYTES, packParams[0]); + gl.glPixelStorei(eGL_PACK_LSB_FIRST, packParams[1]); + gl.glPixelStorei(eGL_PACK_ROW_LENGTH, packParams[2]); + gl.glPixelStorei(eGL_PACK_IMAGE_HEIGHT, packParams[3]); + gl.glPixelStorei(eGL_PACK_SKIP_PIXELS, packParams[4]); + gl.glPixelStorei(eGL_PACK_SKIP_ROWS, packParams[5]); + gl.glPixelStorei(eGL_PACK_SKIP_IMAGES, packParams[6]); + gl.glPixelStorei(eGL_PACK_ALIGNMENT, packParams[7]); + + gl.glPixelStorei(eGL_UNPACK_SWAP_BYTES, unpackParams[0]); + gl.glPixelStorei(eGL_UNPACK_LSB_FIRST, unpackParams[1]); + gl.glPixelStorei(eGL_UNPACK_ROW_LENGTH, unpackParams[2]); + gl.glPixelStorei(eGL_UNPACK_IMAGE_HEIGHT, unpackParams[3]); + gl.glPixelStorei(eGL_UNPACK_SKIP_PIXELS, unpackParams[4]); + gl.glPixelStorei(eGL_UNPACK_SKIP_ROWS, unpackParams[5]); + gl.glPixelStorei(eGL_UNPACK_SKIP_IMAGES, unpackParams[6]); + gl.glPixelStorei(eGL_UNPACK_ALIGNMENT, unpackParams[7]); } gl.glTextureParameterivEXT(res.name, details.curType, eGL_TEXTURE_MAX_LEVEL, (GLint *)&state->maxLevel); @@ -965,7 +1082,50 @@ void GLResourceManager::Apply_InitialState(GLResource live, InitialContentData i // correct MAX_LEVEL is set to whatever the program had. int maxlevel = mips-1; gl.glTextureParameterivEXT(live.name, details.curType, eGL_TEXTURE_MAX_LEVEL, (GLint *)&maxlevel); + + bool iscomp = IsCompressedFormat(details.internalFormat); + + GLint packParams[8]; + GLint unpackParams[8]; + if(iscomp && VendorCheck[VendorCheck_AMD_copy_compressed_tinymips]) + { + gl.glGetIntegerv(eGL_PACK_SWAP_BYTES, &packParams[0]); + gl.glGetIntegerv(eGL_PACK_LSB_FIRST, &packParams[1]); + gl.glGetIntegerv(eGL_PACK_ROW_LENGTH, &packParams[2]); + gl.glGetIntegerv(eGL_PACK_IMAGE_HEIGHT, &packParams[3]); + gl.glGetIntegerv(eGL_PACK_SKIP_PIXELS, &packParams[4]); + gl.glGetIntegerv(eGL_PACK_SKIP_ROWS, &packParams[5]); + gl.glGetIntegerv(eGL_PACK_SKIP_IMAGES, &packParams[6]); + gl.glGetIntegerv(eGL_PACK_ALIGNMENT, &packParams[7]); + gl.glPixelStorei(eGL_PACK_SWAP_BYTES, 0); + gl.glPixelStorei(eGL_PACK_LSB_FIRST, 0); + gl.glPixelStorei(eGL_PACK_ROW_LENGTH, 0); + gl.glPixelStorei(eGL_PACK_IMAGE_HEIGHT, 0); + gl.glPixelStorei(eGL_PACK_SKIP_PIXELS, 0); + gl.glPixelStorei(eGL_PACK_SKIP_ROWS, 0); + gl.glPixelStorei(eGL_PACK_SKIP_IMAGES, 0); + gl.glPixelStorei(eGL_PACK_ALIGNMENT, 1); + + gl.glGetIntegerv(eGL_UNPACK_SWAP_BYTES, &unpackParams[0]); + gl.glGetIntegerv(eGL_UNPACK_LSB_FIRST, &unpackParams[1]); + gl.glGetIntegerv(eGL_UNPACK_ROW_LENGTH, &unpackParams[2]); + gl.glGetIntegerv(eGL_UNPACK_IMAGE_HEIGHT, &unpackParams[3]); + gl.glGetIntegerv(eGL_UNPACK_SKIP_PIXELS, &unpackParams[4]); + gl.glGetIntegerv(eGL_UNPACK_SKIP_ROWS, &unpackParams[5]); + gl.glGetIntegerv(eGL_UNPACK_SKIP_IMAGES, &unpackParams[6]); + gl.glGetIntegerv(eGL_UNPACK_ALIGNMENT, &unpackParams[7]); + + gl.glPixelStorei(eGL_UNPACK_SWAP_BYTES, 0); + gl.glPixelStorei(eGL_UNPACK_LSB_FIRST, 0); + gl.glPixelStorei(eGL_UNPACK_ROW_LENGTH, 0); + gl.glPixelStorei(eGL_UNPACK_IMAGE_HEIGHT, 0); + gl.glPixelStorei(eGL_UNPACK_SKIP_PIXELS, 0); + gl.glPixelStorei(eGL_UNPACK_SKIP_ROWS, 0); + gl.glPixelStorei(eGL_UNPACK_SKIP_IMAGES, 0); + gl.glPixelStorei(eGL_UNPACK_ALIGNMENT, 1); + } + // copy over mips for(int i=0; i < mips; i++) { @@ -980,15 +1140,89 @@ void GLResourceManager::Apply_InitialState(GLResource live, InitialContentData i details.curType == eGL_TEXTURE_2D_ARRAY) d = details.depth; - // it seems like everything explodes if I do glCopyImageSubData on a D32F_S8 texture - on replay loads of things - // get heavily corrupted - probably the same as the problems we get in-program, but magnified. It seems like a driver bug, - // nvidia specific. - // In most cases a program isn't going to rely on the contents of a depth-stencil buffer (shadow maps that it might - // require would be depth-only formatted). - if(details.internalFormat == eGL_DEPTH32F_STENCIL8 && VendorCheck[VendorCheck_NV_avoid_D32S8_copy]) - RDCDEBUG("Not fetching initial contents of D32F_S8 texture"); + // AMD throws an error copying mips that are smaller than the block size in one dimension, so do copy via + // CPU instead (will be slow, potentially we could optimise this if there's a different GPU-side image copy + // routine that works on these dimensions. Hopefully there'll only be a couple of such mips). + if(iscomp && VendorCheck[VendorCheck_AMD_copy_compressed_tinymips] && (w < 4 || h < 4)) + { + 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(details.curType != eGL_TEXTURE_CUBE_MAP) + { + targets[0] = details.curType; + count = 1; + } + + for(int trg=0; trg < count; trg++) + { + GLint compSize; + gl.glGetTextureLevelParameterivEXT(tex, targets[trg], i, eGL_TEXTURE_COMPRESSED_IMAGE_SIZE, &compSize); + + size_t size = compSize; + + // sometimes cubemaps return the compressed image size for the whole texture, but we read it + // face by face + if(VendorCheck[VendorCheck_EXT_compressed_cube_size] && details.curType == eGL_TEXTURE_CUBE_MAP) + size /= 6; + + byte *buf = new byte[size]; + + // read to CPU + gl.glGetCompressedTextureImageEXT(tex, targets[trg], i, buf); + + // write to GPU + if(details.dimension == 1) + gl.glCompressedTextureSubImage1DEXT(live.name, targets[trg], i, 0, w, details.internalFormat, (GLsizei)size, buf); + else if(details.dimension == 2) + gl.glCompressedTextureSubImage2DEXT(live.name, targets[trg], i, 0, 0, w, h, details.internalFormat, (GLsizei)size, buf); + else if(details.dimension == 3) + gl.glCompressedTextureSubImage3DEXT(live.name, targets[trg], i, 0, 0, 0, w, h, d, details.internalFormat, (GLsizei)size, buf); + + delete[] buf; + } + } else - gl.glCopyImageSubData(tex, details.curType, i, 0, 0, 0, live.name, details.curType, i, 0, 0, 0, w, h, d); + { + // it seems like everything explodes if I do glCopyImageSubData on a D32F_S8 texture - on replay loads of things + // get heavily corrupted - probably the same as the problems we get in-program, but magnified. It seems like a driver bug, + // nvidia specific. + // In most cases a program isn't going to rely on the contents of a depth-stencil buffer (shadow maps that it might + // require would be depth-only formatted). + if(details.internalFormat == eGL_DEPTH32F_STENCIL8 && VendorCheck[VendorCheck_NV_avoid_D32S8_copy]) + RDCDEBUG("Not fetching initial contents of D32F_S8 texture"); + else + gl.glCopyImageSubData(tex, details.curType, i, 0, 0, 0, live.name, details.curType, i, 0, 0, 0, w, h, d); + } + } + + if(iscomp && VendorCheck[VendorCheck_AMD_copy_compressed_tinymips]) + { + gl.glPixelStorei(eGL_PACK_SWAP_BYTES, packParams[0]); + gl.glPixelStorei(eGL_PACK_LSB_FIRST, packParams[1]); + gl.glPixelStorei(eGL_PACK_ROW_LENGTH, packParams[2]); + gl.glPixelStorei(eGL_PACK_IMAGE_HEIGHT, packParams[3]); + gl.glPixelStorei(eGL_PACK_SKIP_PIXELS, packParams[4]); + gl.glPixelStorei(eGL_PACK_SKIP_ROWS, packParams[5]); + gl.glPixelStorei(eGL_PACK_SKIP_IMAGES, packParams[6]); + gl.glPixelStorei(eGL_PACK_ALIGNMENT, packParams[7]); + + gl.glPixelStorei(eGL_UNPACK_SWAP_BYTES, unpackParams[0]); + gl.glPixelStorei(eGL_UNPACK_LSB_FIRST, unpackParams[1]); + gl.glPixelStorei(eGL_UNPACK_ROW_LENGTH, unpackParams[2]); + gl.glPixelStorei(eGL_UNPACK_IMAGE_HEIGHT, unpackParams[3]); + gl.glPixelStorei(eGL_UNPACK_SKIP_PIXELS, unpackParams[4]); + gl.glPixelStorei(eGL_UNPACK_SKIP_ROWS, unpackParams[5]); + gl.glPixelStorei(eGL_UNPACK_SKIP_IMAGES, unpackParams[6]); + gl.glPixelStorei(eGL_UNPACK_ALIGNMENT, unpackParams[7]); } TextureStateInitialData *state = (TextureStateInitialData *)initial.blob;