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