From a1a0b1a7ec47767e498e62de3932ae96239c3817 Mon Sep 17 00:00:00 2001 From: akharlamov Date: Fri, 9 Nov 2018 18:20:41 -0800 Subject: [PATCH] Add ability to save .rdc file thumbnail in formats other than JPEG By default RenderDoc saves captured frame thumbnail image as JPEG which is lossy compression and makes it difficult to use as a reference image for testing. Added an option to save thumbnails in different formats. In order not to change the binary serialization format of .rdc files, if the thumbnail format is not JPEG, a default JPEG thumbnail is still saved in the file header as before, while the actual thumbnail is saved as an additional chunk in the binary sections list. Older versions of RenderDoc will just ignore this section and still use the legacy JPEG thumbnail. Change-Id: Icbf4fbd629719b49868fb785a656f6c7c9946ef9 --- renderdoc/api/replay/renderdoc_tostr.inl | 1 + renderdoc/api/replay/replay_enums.h | 8 +++ renderdoc/core/core.cpp | 26 +++++++- renderdoc/core/core.h | 2 +- renderdoc/driver/d3d11/d3d11_device.cpp | 4 +- renderdoc/driver/d3d12/d3d12_device.cpp | 2 +- renderdoc/driver/gl/gl_driver.cpp | 6 +- renderdoc/driver/vulkan/vk_core.cpp | 2 +- renderdoc/replay/capture_file.cpp | 43 +++++++++---- renderdoc/serialise/rdcfile.cpp | 77 +++++++++++++++++++++++- renderdoc/serialise/rdcfile.h | 9 +++ 11 files changed, 159 insertions(+), 21 deletions(-) diff --git a/renderdoc/api/replay/renderdoc_tostr.inl b/renderdoc/api/replay/renderdoc_tostr.inl index 4b1e8098e..fec59bd12 100644 --- a/renderdoc/api/replay/renderdoc_tostr.inl +++ b/renderdoc/api/replay/renderdoc_tostr.inl @@ -852,6 +852,7 @@ std::string DoStringise(const SectionType &el) STRINGISE_ENUM_CLASS_NAMED(Notes, "renderdoc/ui/notes"); STRINGISE_ENUM_CLASS_NAMED(ResourceRenames, "renderdoc/ui/resrenames"); STRINGISE_ENUM_CLASS_NAMED(AMDRGPProfile, "amd/rgp/profile"); + STRINGISE_ENUM_CLASS_NAMED(ExtendedThumbnail, "renderdoc/internal/exthumb"); } END_ENUM_STRINGISE(); } diff --git a/renderdoc/api/replay/replay_enums.h b/renderdoc/api/replay/replay_enums.h index a65b69202..53f7c4f33 100644 --- a/renderdoc/api/replay/replay_enums.h +++ b/renderdoc/api/replay/replay_enums.h @@ -76,6 +76,13 @@ version of RenderDoc that addes a new section type. They should be considered eq This section contains a .rgp profile from AMD's RGP tool, which can be extracted and loaded. The name for this section will be "amd/rgp/profile". + +.. data:: ExtendedThumbnail + + This section contains a thumbnail in format other than JPEG. For example, when it needs to be + lossless. + + The name for this section will be "renderdoc/internal/exthumb". )"); enum class SectionType : uint32_t { @@ -87,6 +94,7 @@ enum class SectionType : uint32_t Notes, ResourceRenames, AMDRGPProfile, + ExtendedThumbnail, Count, }; diff --git a/renderdoc/core/core.cpp b/renderdoc/core/core.cpp index 05fc5b40f..910279e9a 100644 --- a/renderdoc/core/core.cpp +++ b/renderdoc/core/core.cpp @@ -705,7 +705,7 @@ bool RenderDoc::ShouldTriggerCapture(uint32_t frameNumber) } RDCFile *RenderDoc::CreateRDC(RDCDriver driver, uint32_t frameNum, void *thpixels, size_t thlen, - uint16_t thwidth, uint16_t thheight) + uint16_t thwidth, uint16_t thheight, FileType thformat) { RDCFile *ret = new RDCFile; @@ -734,6 +734,9 @@ RDCFile *RenderDoc::CreateRDC(RDCDriver driver, uint32_t frameNum, void *thpixel th.pixels = (const byte *)thpixels; th.width = thwidth; th.height = thheight; + RDCASSERT(thformat == FileType::JPG || thformat == FileType::PNG || thformat == FileType::BMP || + thformat == FileType::TGA || thformat == FileType::Raw); + th.format = thformat; thumb = &th; } @@ -1092,6 +1095,27 @@ void RenderDoc::FinishCaptureWriting(RDCFile *rdc, uint32_t frameNumber) delete w; } + const RDCThumb &thumb = rdc->GetThumbnail(); + if(thumb.format != FileType::JPG && thumb.width > 0 && thumb.height > 0) + { + SectionProperties props = {}; + props.type = SectionType::ExtendedThumbnail; + props.version = 1; + StreamWriter *w = rdc->WriteSection(props); + + ExtThumbnailHeader header; + header.width = thumb.width; + header.height = thumb.height; + header.len = thumb.len; + header.format = (uint32_t)thumb.format; + w->Write(header); + w->Write(thumb.pixels, thumb.len); + + w->Finish(); + + delete w; + } + RDCLOG("Written to disk: %s", m_CurrentLogFile.c_str()); CaptureData cap(m_CurrentLogFile, Timing::GetUnixTimestamp(), rdc->GetDriver(), frameNumber); diff --git a/renderdoc/core/core.h b/renderdoc/core/core.h index b0ac732b8..58484daae 100644 --- a/renderdoc/core/core.h +++ b/renderdoc/core/core.h @@ -392,7 +392,7 @@ public: void UnloadCrashHandler(); ICrashHandler *GetCrashHandler() const { return m_ExHandler; } RDCFile *CreateRDC(RDCDriver driver, uint32_t frameNum, void *thpixels, size_t thlen, - uint16_t thwidth, uint16_t thheight); + uint16_t thwidth, uint16_t thheight, FileType thformat); void FinishCaptureWriting(RDCFile *rdc, uint32_t frameNumber); void AddChildProcess(uint32_t pid, uint32_t ident) diff --git a/renderdoc/driver/d3d11/d3d11_device.cpp b/renderdoc/driver/d3d11/d3d11_device.cpp index 10df5f8e1..a15173a96 100644 --- a/renderdoc/driver/d3d11/d3d11_device.cpp +++ b/renderdoc/driver/d3d11/d3d11_device.cpp @@ -1677,8 +1677,8 @@ bool WrappedID3D11Device::EndFrameCapture(void *dev, void *wnd) } } - RDCFile *rdc = RenderDoc::Inst().CreateRDC( - RDCDriver::D3D11, m_CapturedFrames.back().frameNumber, jpgbuf, len, thwidth, thheight); + RDCFile *rdc = RenderDoc::Inst().CreateRDC(RDCDriver::D3D11, m_CapturedFrames.back().frameNumber, + jpgbuf, len, thwidth, thheight, FileType::JPG); SAFE_DELETE_ARRAY(jpgbuf); SAFE_DELETE_ARRAY(thpixels); diff --git a/renderdoc/driver/d3d12/d3d12_device.cpp b/renderdoc/driver/d3d12/d3d12_device.cpp index 7952ffd66..6d67f66c3 100644 --- a/renderdoc/driver/d3d12/d3d12_device.cpp +++ b/renderdoc/driver/d3d12/d3d12_device.cpp @@ -1647,7 +1647,7 @@ bool WrappedID3D12Device::EndFrameCapture(void *dev, void *wnd) WrappedID3D12Resource::RefBuffers(GetResourceManager()); rdc = RenderDoc::Inst().CreateRDC(RDCDriver::D3D12, m_CapturedFrames.back().frameNumber, jpgbuf, - len, thwidth, thheight); + len, thwidth, thheight, FileType::JPG); SAFE_DELETE_ARRAY(jpgbuf); SAFE_DELETE_ARRAY(thpixels); diff --git a/renderdoc/driver/gl/gl_driver.cpp b/renderdoc/driver/gl/gl_driver.cpp index efcff68a1..dbade716a 100644 --- a/renderdoc/driver/gl/gl_driver.cpp +++ b/renderdoc/driver/gl/gl_driver.cpp @@ -1891,9 +1891,9 @@ bool WrappedOpenGL::EndFrameCapture(void *dev, void *wnd) if(bbim == NULL) bbim = SaveBackbufferImage(); - RDCFile *rdc = - RenderDoc::Inst().CreateRDC(GetDriverType(), m_CapturedFrames.back().frameNumber, - bbim->jpgbuf, bbim->len, bbim->thwidth, bbim->thheight); + RDCFile *rdc = RenderDoc::Inst().CreateRDC(GetDriverType(), m_CapturedFrames.back().frameNumber, + bbim->jpgbuf, bbim->len, bbim->thwidth, + bbim->thheight, FileType::JPG); SAFE_DELETE(bbim); diff --git a/renderdoc/driver/vulkan/vk_core.cpp b/renderdoc/driver/vulkan/vk_core.cpp index 063cae473..f59b545cf 100644 --- a/renderdoc/driver/vulkan/vk_core.cpp +++ b/renderdoc/driver/vulkan/vk_core.cpp @@ -1667,7 +1667,7 @@ bool WrappedVulkan::EndFrameCapture(void *dev, void *wnd) } RDCFile *rdc = RenderDoc::Inst().CreateRDC(RDCDriver::Vulkan, m_CapturedFrames.back().frameNumber, - jpgbuf, len, thwidth, thheight); + jpgbuf, len, thwidth, thheight, FileType::JPG); SAFE_DELETE_ARRAY(jpgbuf); SAFE_DELETE_ARRAY(thpixels); diff --git a/renderdoc/replay/capture_file.cpp b/renderdoc/replay/capture_file.cpp index 6e4d2a68a..fe100722b 100644 --- a/renderdoc/replay/capture_file.cpp +++ b/renderdoc/replay/capture_file.cpp @@ -549,20 +549,20 @@ Thumbnail CaptureFile::GetThumbnail(FileType type, uint32_t maxsize) const RDCThumb &thumb = m_RDC->GetThumbnail(); - const byte *jpgbuf = thumb.pixels; + const byte *thumbbuf = thumb.pixels; size_t thumblen = thumb.len; uint32_t thumbwidth = thumb.width, thumbheight = thumb.height; - if(jpgbuf == NULL) + if(thumbbuf == NULL) return ret; bytebuf buf; - // if the desired output is jpg and either there's no max size or it's already satisfied, - // return the data directly - if(type == FileType::JPG && (maxsize == 0 || (maxsize > thumbwidth && maxsize > thumbheight))) + // if the desired output is the format of stored thumbnail and either there's no max size or it's + // already satisfied, return the data directly + if(type == thumb.format && (maxsize == 0 || (maxsize > thumbwidth && maxsize > thumbheight))) { - buf.assign(jpgbuf, thumblen); + buf.assign(thumbbuf, thumblen); } else { @@ -571,8 +571,28 @@ Thumbnail CaptureFile::GetThumbnail(FileType type, uint32_t maxsize) int w = (int)thumbwidth; int h = (int)thumbheight; int comp = 3; - byte *thumbpixels = - jpgd::decompress_jpeg_image_from_memory(jpgbuf, (int)thumblen, &w, &h, &comp, 3); + const byte *thumbpixels = NULL; + byte *allocatedBuffer = NULL; + switch(thumb.format) + { + case FileType::JPG: + allocatedBuffer = + jpgd::decompress_jpeg_image_from_memory(thumbbuf, (int)thumblen, &w, &h, &comp, 3); + thumbpixels = allocatedBuffer; + break; + + case FileType::Raw: thumbpixels = thumbbuf; break; + + default: + allocatedBuffer = stbi_load_from_memory(thumbbuf, (int)thumblen, &w, &h, &comp, 3); + if(allocatedBuffer == NULL) + { + RDCERR("Couldn't decode provided thumbnail"); + return ret; + } + thumbpixels = allocatedBuffer; + break; + } if(maxsize != 0) { @@ -595,8 +615,9 @@ Thumbnail CaptureFile::GetThumbnail(FileType type, uint32_t maxsize) stbir_resize_uint8_srgb(thumbpixels, thumbwidth, thumbheight, 0, resizedpixels, clampedWidth, clampedHeight, 0, 3, -1, 0); - free(thumbpixels); + free(allocatedBuffer); + allocatedBuffer = resizedpixels; thumbpixels = resizedpixels; thumbwidth = clampedWidth; thumbheight = clampedHeight; @@ -644,7 +665,7 @@ Thumbnail CaptureFile::GetThumbnail(FileType type, uint32_t maxsize) default: { RDCERR("Unsupported file type %d in thumbnail fetch", type); - free(thumbpixels); + free(allocatedBuffer); ret.width = 0; ret.height = 0; return ret; @@ -653,7 +674,7 @@ Thumbnail CaptureFile::GetThumbnail(FileType type, uint32_t maxsize) buf = encodedBytes; - free(thumbpixels); + free(allocatedBuffer); } ret.data.swap(buf); diff --git a/renderdoc/serialise/rdcfile.cpp b/renderdoc/serialise/rdcfile.cpp index 8c5198c3d..0c2558ae3 100644 --- a/renderdoc/serialise/rdcfile.cpp +++ b/renderdoc/serialise/rdcfile.cpp @@ -24,6 +24,7 @@ #include "rdcfile.h" #include +#include "3rdparty/jpeg-compressor/jpge.h" #include "3rdparty/stb/stb_image.h" #include "api/replay/version.h" #include "common/dds_readwrite.h" @@ -387,6 +388,7 @@ void RDCFile::Init(StreamReader &reader) m_Thumb.width = thumb.width; m_Thumb.height = thumb.height; m_Thumb.len = thumb.length; + m_Thumb.format = FileType::JPG; if(m_Thumb.len > 0 && m_Thumb.width > 0 && m_Thumb.height > 0) { @@ -578,6 +580,36 @@ void RDCFile::Init(StreamReader &reader) { RETURNERROR(ContainerError::Corrupt, "Capture file doesn't have a frame capture"); } + + int index = SectionIndex(SectionType::ExtendedThumbnail); + if(index >= 0) + { + StreamReader *thumbReader = ReadSection(index); + if(thumbReader) + { + ExtThumbnailHeader thumbHeader; + if(thumbReader->Read(thumbHeader)) + { + thumbData = new byte[thumbHeader.len]; + bool succeeded = thumbReader->Read(thumbData, thumbHeader.len) && !thumbReader->IsErrored(); + if(succeeded && thumbHeader.format < (uint32_t)FileType::Count) + { + m_Thumb.width = thumbHeader.width; + m_Thumb.height = thumbHeader.height; + m_Thumb.len = thumbHeader.len; + m_Thumb.format = (FileType)thumbHeader.format; + delete[] m_Thumb.pixels; + m_Thumb.pixels = thumbData; + } + else + { + delete[] thumbData; + } + thumbData = NULL; + } + delete thumbReader; + } + } } bool RDCFile::CopyFileTo(const char *filename) @@ -641,8 +673,50 @@ void RDCFile::Create(const char *filename) thumbHeader.width = m_Thumb.width; thumbHeader.height = m_Thumb.height; + const byte *jpgPixels = m_Thumb.pixels; thumbHeader.length = m_Thumb.len; + byte *jpgBuffer = NULL; + if(m_Thumb.format != FileType::JPG && m_Thumb.width > 0 && m_Thumb.height > 0) + { + // the primary thumbnail must be in JPG format, must perform conversion + const byte *rawPixels = NULL; + byte *rawBuffer = NULL; + int w = (int)m_Thumb.width; + int h = (int)m_Thumb.height; + int comp = 3; + + if(m_Thumb.format == FileType::Raw) + { + rawPixels = m_Thumb.pixels; + } + else + { + rawBuffer = stbi_load_from_memory(m_Thumb.pixels, (int)m_Thumb.len, &w, &h, &comp, 3); + rawPixels = rawBuffer; + } + + if(rawPixels) + { + int len = w * h * comp; + jpgBuffer = new byte[len]; + jpge::params p; + p.m_quality = 90; + jpge::compress_image_to_jpeg_file_in_memory(jpgBuffer, len, w, h, comp, rawPixels, p); + thumbHeader.length = (uint32_t)len; + jpgPixels = jpgBuffer; + } + else + { + thumbHeader.width = 0; + thumbHeader.height = 0; + thumbHeader.length = 0; + jpgPixels = NULL; + } + if(rawBuffer) + stbi_image_free(rawBuffer); + } + CaptureMetaData meta; meta.driverID = m_Driver; meta.machineIdent = m_MachineIdent; @@ -658,12 +732,13 @@ void RDCFile::Create(const char *filename) writer.Write(&thumbHeader, offsetof(BinaryThumbnail, data)); if(thumbHeader.length > 0) - writer.Write(m_Thumb.pixels, thumbHeader.length); + writer.Write(jpgPixels, thumbHeader.length); writer.Write(&meta, offsetof(CaptureMetaData, driverName)); writer.Write(m_DriverName.c_str(), meta.driverNameLength); + delete[] jpgBuffer; if(writer.IsErrored()) { RETURNERROR(ContainerError::FileIO, "Error writing file header"); diff --git a/renderdoc/serialise/rdcfile.h b/renderdoc/serialise/rdcfile.h index a972c40dd..b14aabe2e 100644 --- a/renderdoc/serialise/rdcfile.h +++ b/renderdoc/serialise/rdcfile.h @@ -44,6 +44,15 @@ struct RDCThumb uint32_t len = 0; uint16_t width = 0; uint16_t height = 0; + FileType format = FileType::JPG; +}; + +struct ExtThumbnailHeader +{ + uint16_t width; + uint16_t height; + uint32_t len; + uint32_t format; }; class RDCFile