mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-08-08 07:41:12 +00:00
Remap block-compressed 3D textures to 2D arrays on GL. Closes #2186
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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++)
|
||||
{
|
||||
|
||||
@@ -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<rdcpair<size_t, size_t>> subresources;
|
||||
};
|
||||
|
||||
struct write_dds_data : public dds_data
|
||||
{
|
||||
rdcarray<byte *> 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);
|
||||
|
||||
+133
-99
@@ -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<rdcpair<size_t, size_t>> 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user