diff --git a/renderdoc/api/replay/data_types.h b/renderdoc/api/replay/data_types.h index 8391e617f..955569ef5 100644 --- a/renderdoc/api/replay/data_types.h +++ b/renderdoc/api/replay/data_types.h @@ -301,6 +301,30 @@ Invalid values will result in 1 being set. flags |= ResourceFormat_3Planes; } + DOCUMENT(R"(:return: ``True`` if the ``ResourceFormat`` is a block-compressed type. +:rtype: bool +)"); + bool BlockFormat() const + { + switch(type) + { + default: break; + case ResourceFormatType::BC1: + case ResourceFormatType::BC4: + case ResourceFormatType::BC2: + case ResourceFormatType::BC3: + case ResourceFormatType::BC5: + case ResourceFormatType::BC6: + case ResourceFormatType::BC7: + case ResourceFormatType::ETC2: + case ResourceFormatType::EAC: + case ResourceFormatType::ASTC: + case ResourceFormatType::PVRTC: return true; + } + + return false; + } + DOCUMENT(R"(Return the size of a single element in this format, usually a pixel. For regular sized formats this is just :data:`compByteWidth` times :data:`compCount`, for special packed formats it's the tightly packed size of a single element, with no padding. diff --git a/renderdoc/common/dds_readwrite.cpp b/renderdoc/common/dds_readwrite.cpp index 74060634d..4625c5d03 100644 --- a/renderdoc/common/dds_readwrite.cpp +++ b/renderdoc/common/dds_readwrite.cpp @@ -677,7 +677,7 @@ DXGI_FORMAT ResourceFormat2DXGIFormat(ResourceFormat format) return DXGI_FORMAT_UNKNOWN; } -bool write_dds_to_file(FILE *f, const dds_data &data) +bool write_dds_to_file(FILE *f, const write_dds_data &data) { if(!f) return false; @@ -914,7 +914,7 @@ bool write_dds_to_file(FILE *f, const dds_data &data) uint32_t numdepths = RDCMAX(1U, data.depth >> mip); for(uint32_t d = 0; d < numdepths; d++) { - byte *bytedata = data.subdata[i]; + byte *bytedata = data.subresources[d]; uint32_t rowlen = RDCMAX(1U, data.width >> mip); uint32_t numRows = RDCMAX(1U, data.height >> mip); @@ -958,10 +958,10 @@ bool is_dds_file(byte *headerBuffer, size_t size) return memcmp(headerBuffer, &dds_fourcc, 4) == 0; } -dds_data load_dds_from_file(StreamReader *reader) +read_dds_data load_dds_from_file(StreamReader *reader) { - dds_data ret = {}; - dds_data error = {}; + read_dds_data ret = {}; + read_dds_data error = {}; uint64_t fileSize = reader->GetSize(); @@ -1195,8 +1195,28 @@ dds_data load_dds_from_file(StreamReader *reader) return ret; } - ret.subsizes = new uint32_t[ret.slices * ret.mips]; - ret.subdata = new byte *[ret.slices * ret.mips]; + // we reserve space for a full mip-chain (twice the size of the top mip) just to be conservative + { + uint32_t rowlen = AlignUp(ret.width, subsamplePacking); + uint32_t numRows = ret.height; + uint32_t pitch = RDCMAX(1U, rowlen * bytesPerPixel); + + // pitch/rows are in blocks, not pixels, for block formats. + if(blockFormat) + { + numRows = RDCMAX(1U, (numRows + 3) / 4); + + uint32_t blockSize = + (ret.format.type == ResourceFormatType::BC1 || ret.format.type == ResourceFormatType::BC4) + ? 8 + : 16; + + pitch = RDCMAX(blockSize, (((rowlen + 3) / 4)) * blockSize); + } + + ret.buffer.reserve(ret.slices * 2 * ret.depth * numRows * pitch); + } + ret.subresources.reserve(ret.slices * ret.mips); int i = 0; for(uint32_t slice = 0; slice < ret.slices; slice++) @@ -1222,9 +1242,14 @@ dds_data load_dds_from_file(StreamReader *reader) pitch = RDCMAX(blockSize, (((rowlen + 3) / 4)) * blockSize); } - ret.subsizes[i] = numdepths * numRows * pitch; + size_t subOffs = ret.buffer.size(); + size_t subSize = numdepths * numRows * pitch; - byte *bytedata = ret.subdata[i] = new byte[ret.subsizes[i]]; + ret.subresources.push_back({subOffs, subSize}); + + ret.buffer.resize(ret.buffer.size() + subSize); + + byte *bytedata = ret.buffer.data() + subOffs; for(uint32_t d = 0; d < numdepths; d++) { diff --git a/renderdoc/common/dds_readwrite.h b/renderdoc/common/dds_readwrite.h index 9f34a4cba..358735f92 100644 --- a/renderdoc/common/dds_readwrite.h +++ b/renderdoc/common/dds_readwrite.h @@ -40,11 +40,21 @@ struct dds_data bool cubemap; ResourceFormat format; +}; - byte **subdata; - uint32_t *subsizes; +struct read_dds_data : public dds_data +{ + bytebuf buffer; + + // pairs of {offset, size} into above data buffer + rdcarray> subresources; +}; + +struct write_dds_data : public dds_data +{ + rdcarray subresources; }; extern bool is_dds_file(byte *headerBuffer, size_t size); -extern dds_data load_dds_from_file(StreamReader *reader); -extern bool write_dds_to_file(FILE *f, const dds_data &data); +extern read_dds_data load_dds_from_file(StreamReader *reader); +extern bool write_dds_to_file(FILE *f, const write_dds_data &data); diff --git a/renderdoc/core/image_viewer.cpp b/renderdoc/core/image_viewer.cpp index c396bf4b5..caba56ea6 100644 --- a/renderdoc/core/image_viewer.cpp +++ b/renderdoc/core/image_viewer.cpp @@ -327,6 +327,7 @@ public: void FileChanged() { RefreshFile(); } private: void RefreshFile(); + void CreateProxyTexture(TextureDescription &texDetails, read_dds_data &read_data); APIProperties m_Props; FrameRecord m_FrameRecord; @@ -421,20 +422,14 @@ ReplayStatus IMG_CreateReplayDevice(RDCFile *rdc, IReplayDriver **driver) { FileIO::fseek64(f, 0, SEEK_SET); StreamReader reader(f); - dds_data read_data = load_dds_from_file(&reader); + read_dds_data read_data = load_dds_from_file(&reader); f = NULL; - if(read_data.subdata == NULL) + if(read_data.subresources.empty()) { RDCERR("DDS file recognised, but couldn't load"); return ReplayStatus::ImageUnsupported; } - - for(uint32_t i = 0; i < read_data.slices * read_data.mips; i++) - delete[] read_data.subdata[i]; - - delete[] read_data.subdata; - delete[] read_data.subsizes; } else { @@ -703,7 +698,7 @@ void ImageViewer::RefreshFile() m_FrameRecord.frameInfo.persistentSize = 0; m_FrameRecord.frameInfo.uncompressedFileSize = datasize; - dds_data read_data = {0}; + read_dds_data read_data; if(dds) { @@ -712,10 +707,8 @@ void ImageViewer::RefreshFile() read_data = load_dds_from_file(&reader); f = NULL; - if(read_data.subdata == NULL) - { + if(read_data.subresources.empty()) return; - } texDetails.cubemap = read_data.cubemap; texDetails.arraysize = read_data.slices; @@ -750,17 +743,14 @@ void ImageViewer::RefreshFile() m_FrameRecord.frameInfo.uncompressedFileSize = 0; for(uint32_t i = 0; i < texDetails.arraysize * texDetails.mips; i++) - m_FrameRecord.frameInfo.uncompressedFileSize += read_data.subsizes[i]; + m_FrameRecord.frameInfo.uncompressedFileSize += read_data.subresources[i].second; } m_FrameRecord.frameInfo.compressedFileSize = m_FrameRecord.frameInfo.uncompressedFileSize; // recreate proxy texture if necessary. - // we rewrite the texture IDs so that the - // outside world doesn't need to know about this - // (we only ever have one texture in the image - // viewer so we can just set all texture IDs - // used to that). + // we rewrite the texture IDs so that the outside world doesn't need to know about this (we only + // ever have one texture in the image viewer so we can just set all texture IDs used to that). if(m_TextureID != ResourceId()) { if(m_TexDetails.width != texDetails.width || m_TexDetails.height != texDetails.height || @@ -773,81 +763,7 @@ void ImageViewer::RefreshFile() } if(m_TextureID == ResourceId()) - { - if(m_Proxy->IsTextureSupported(texDetails)) - { - m_TextureID = m_Proxy->CreateProxyTexture(texDetails); - } - else - { - if(dds) - { - // see if we can convert this format on the CPU for proxying - bool convertSupported = false; - DecodeFormattedComponents(texDetails.format, NULL, &convertSupported); - - if(convertSupported) - { - uint32_t srcStride = texDetails.format.ElementSize(); - - if(texDetails.format.type == ResourceFormatType::D16S8) - srcStride = 4; - else if(texDetails.format.type == ResourceFormatType::D32S8) - srcStride = 8; - - m_RealTexData.resize(texDetails.arraysize * texDetails.mips); - - for(uint32_t i = 0; i < texDetails.arraysize * texDetails.mips; i++) - { - const uint32_t mip = i % texDetails.mips; - - const uint32_t mipwidth = RDCMAX(1U, texDetails.width >> mip); - const uint32_t mipheight = RDCMAX(1U, texDetails.height >> mip); - const uint32_t mipdepth = RDCMAX(1U, texDetails.depth >> mip); - - byte *old = read_data.subdata[i]; - m_RealTexData[i].assign(old, read_data.subsizes[i]); - - read_data.subsizes[i] = sizeof(FloatVector) * mipwidth * mipheight * mipdepth; - byte *converted = new byte[read_data.subsizes[i]]; - - byte *src = old; - FloatVector *dst = (FloatVector *)converted; - - for(uint32_t z = 0; z < mipdepth; z++) - { - for(uint32_t y = 0; y < mipheight; y++) - { - for(uint32_t x = 0; x < mipwidth; x++) - { - *dst = DecodeFormattedComponents(texDetails.format, src); - dst++; - src += srcStride; - } - } - } - - read_data.subdata[i] = converted; - delete[] old; - } - - TextureDescription remapped = texDetails; - remapped.format = rgba32_float; - m_TextureID = m_Proxy->CreateProxyTexture(remapped); - } - else - { - RDCLOG("Format %s not supported for local display and can't be converted manually.", - texDetails.format.Name().c_str()); - } - } - else - { - RDCERR("Standard format %s expected to be supported for local display but can't.", - texDetails.format.Name().c_str()); - } - } - } + CreateProxyTexture(texDetails, read_data); if(m_TextureID == ResourceId()) RDCERR("Couldn't create proxy texture for image file"); @@ -866,15 +782,133 @@ void ImageViewer::RefreshFile() for(uint32_t i = 0; i < texDetails.arraysize * texDetails.mips; i++) { m_Proxy->SetProxyTextureData(m_TextureID, {i % texDetails.mips, i / texDetails.mips}, - read_data.subdata[i], (size_t)read_data.subsizes[i]); - - delete[] read_data.subdata[i]; + read_data.buffer.data() + read_data.subresources[i].first, + read_data.subresources[i].second); } - - delete[] read_data.subdata; - delete[] read_data.subsizes; } if(f != NULL) FileIO::fclose(f); } + +void ImageViewer::CreateProxyTexture(TextureDescription &texDetails, read_dds_data &read_data) +{ + if(m_Proxy->IsTextureSupported(texDetails)) + { + m_TextureID = m_Proxy->CreateProxyTexture(texDetails); + return; + } + else + { + // for block compressed 3D textures these may not be supported, try to remap to a 2D array + if(texDetails.format.BlockFormat() && texDetails.type == TextureType::Texture3D) + { + TextureDescription arrayDetails = texDetails; + arrayDetails.arraysize = arrayDetails.depth; + arrayDetails.depth = 1; + arrayDetails.type = TextureType::Texture2DArray; + arrayDetails.dimension = 2; + + if(m_Proxy->IsTextureSupported(arrayDetails)) + { + texDetails = arrayDetails; + m_TextureID = m_Proxy->CreateProxyTexture(arrayDetails); + + rdcarray> oldSubs; + oldSubs.swap(read_data.subresources); + + // reformat the subresources. The data doesn't change we just add new offsets/sizes + for(uint32_t i = 0; i < texDetails.arraysize * texDetails.mips; i++) + { + const uint32_t mip = i % texDetails.mips; + const uint32_t slice = i / texDetails.mips; + + // size of each subresource is 1/Nth for an N-sized array + size_t size = oldSubs[mip].second / texDetails.arraysize; + + // and the offset is slice steps further on + size_t offset = oldSubs[mip].first + size * slice; + + read_data.subresources.push_back({offset, size}); + } + + return; + } + } + + if(read_data.width != 0) + { + // see if we can convert this format on the CPU for proxying + bool convertSupported = false; + DecodeFormattedComponents(texDetails.format, NULL, &convertSupported); + + if(convertSupported) + { + uint32_t srcStride = texDetails.format.ElementSize(); + + if(texDetails.format.type == ResourceFormatType::D16S8) + srcStride = 4; + else if(texDetails.format.type == ResourceFormatType::D32S8) + srcStride = 8; + + m_RealTexData.resize(texDetails.arraysize * texDetails.mips); + + bytebuf convertedData; + + for(uint32_t i = 0; i < texDetails.arraysize * texDetails.mips; i++) + { + const uint32_t mip = i % texDetails.mips; + + const uint32_t mipwidth = RDCMAX(1U, texDetails.width >> mip); + const uint32_t mipheight = RDCMAX(1U, texDetails.height >> mip); + const uint32_t mipdepth = RDCMAX(1U, texDetails.depth >> mip); + + byte *old = read_data.buffer.data() + read_data.subresources[i].first; + m_RealTexData[i].assign(old, read_data.subresources[i].second); + + read_data.subresources[i].first = convertedData.size(); + read_data.subresources[i].second = sizeof(FloatVector) * mipwidth * mipheight * mipdepth; + convertedData.resize(convertedData.size() + read_data.subresources[i].second); + byte *converted = convertedData.data() + read_data.subresources[i].first; + + byte *src = old; + FloatVector *dst = (FloatVector *)converted; + + for(uint32_t z = 0; z < mipdepth; z++) + { + for(uint32_t y = 0; y < mipheight; y++) + { + for(uint32_t x = 0; x < mipwidth; x++) + { + *dst = DecodeFormattedComponents(texDetails.format, src); + dst++; + src += srcStride; + } + } + } + } + + read_data.buffer.swap(convertedData); + + ResourceFormat rgba32_float; + rgba32_float.type = ResourceFormatType::Regular; + rgba32_float.compByteWidth = 4; + rgba32_float.compCount = 4; + rgba32_float.compType = CompType::Float; + + texDetails.format = rgba32_float; + m_TextureID = m_Proxy->CreateProxyTexture(texDetails); + } + else + { + RDCLOG("Format %s not supported for local display and can't be converted manually.", + texDetails.format.Name().c_str()); + } + } + else + { + RDCERR("Standard format %s expected to be supported for local display but can't.", + texDetails.format.Name().c_str()); + } + } +} diff --git a/renderdoc/os/win32/win32_shellext.cpp b/renderdoc/os/win32/win32_shellext.cpp index a5abe7be2..a3961e3de 100644 --- a/renderdoc/os/win32/win32_shellext.cpp +++ b/renderdoc/os/win32/win32_shellext.cpp @@ -53,7 +53,7 @@ struct RDCThumbnailProvider : public IThumbnailProvider, IInitializeWithStream unsigned int m_iRefcount; bool m_Inited; RDCThumb m_Thumb; - dds_data m_ddsData; + read_dds_data m_ddsData; RDCThumbnailProvider() : m_iRefcount(1), m_Inited(false) { InterlockedIncrement(&numProviders); } virtual ~RDCThumbnailProvider() { InterlockedDecrement(&numProviders); } @@ -140,7 +140,7 @@ struct RDCThumbnailProvider : public IThumbnailProvider, IInitializeWithStream StreamReader reader(captureHeader.data(), (ULONG)size); m_ddsData = load_dds_from_file(&reader); - if(m_ddsData.subdata == NULL) + if(m_ddsData.subresources.empty()) { return E_INVALIDARG; } @@ -148,18 +148,13 @@ struct RDCThumbnailProvider : public IThumbnailProvider, IInitializeWithStream // bitmap. m_Thumb.height = (uint16_t)m_ddsData.height; m_Thumb.width = (uint16_t)m_ddsData.width; - size_t len = m_ddsData.subsizes[0]; // size of slice 0 - m_Thumb.pixels.resize(len); - memcpy(m_Thumb.pixels.data(), m_ddsData.subdata[0], len); // slice 0 - m_Thumb.format = FileType::DDS; - // We don't need any other data - for(uint32_t i = 0; i < m_ddsData.slices * m_ddsData.mips; i++) - { - delete[] m_ddsData.subdata[i]; - } - delete[] m_ddsData.subsizes; - delete[] m_ddsData.subdata; + // size of slice 0 + size_t len = m_ddsData.subresources[0].second; + m_Thumb.pixels.resize(len); + // slice 0 data + memcpy(m_Thumb.pixels.data(), m_ddsData.buffer.data() + m_ddsData.subresources[0].first, len); + m_Thumb.format = FileType::DDS; } else { diff --git a/renderdoc/replay/replay_controller.cpp b/renderdoc/replay/replay_controller.cpp index ad1c2b792..4753d7889 100644 --- a/renderdoc/replay/replay_controller.cpp +++ b/renderdoc/replay/replay_controller.cpp @@ -1159,7 +1159,7 @@ bool ReplayController::SaveTexture(const TextureSave &saveData, const rdcstr &pa { if(sd.destType == FileType::DDS) { - dds_data ddsData; + write_dds_data ddsData; ResourceFormat saveFmt = td.format; // use typeCast to inform typeless saving, otherwise it will get lost @@ -1172,7 +1172,7 @@ bool ReplayController::SaveTexture(const TextureSave &saveData, const rdcstr &pa ddsData.format = saveFmt; ddsData.mips = numMips; ddsData.slices = numSlices / td.depth; - ddsData.subdata = &subdata[0]; + ddsData.subresources = subdata; ddsData.cubemap = td.cubemap && numSlices == 6; if(singleSlice)