Remap block-compressed 3D textures to 2D arrays on GL. Closes #2186

This commit is contained in:
baldurk
2021-02-22 12:38:43 +00:00
parent 3025cf66c6
commit 11268b358a
6 changed files with 215 additions and 127 deletions
+24
View File
@@ -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.
+34 -9
View File
@@ -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++)
{
+14 -4
View File
@@ -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
View File
@@ -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());
}
}
}
+8 -13
View File
@@ -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
{
+2 -2
View File
@@ -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)