Refactor: factor out load_exr_from_file

Move all of EXR reading/preparation code into load_exr_from_file,
similar to how load_dds_from_file is done.

ImageViewer::RefreshFile code now uses very similar flow
for both DDS and EXR files (i.e. "what can have mipmaps")
This commit is contained in:
Aras Pranckevicius
2024-11-04 13:51:13 +02:00
committed by Baldur Karlsson
parent 02fd7ac0cd
commit 0193703037
+218 -221
View File
@@ -584,6 +584,205 @@ RDResult IMG_CreateReplayDevice(RDCFile *rdc, IReplayDriver **driver)
return ResultCode::Succeeded;
}
static RDResult load_exr_from_file(FILE *f, uint64_t fileSize, read_tex_data &read_data)
{
// read file into memory
bytebuf buffer;
buffer.resize((size_t)fileSize);
FileIO::fseek64(f, 0, SEEK_SET);
FileIO::fread(buffer.data(), 1, buffer.size(), f);
// parse and check version
EXRVersion exrVersion;
int ret = ParseEXRVersionFromMemory(&exrVersion, buffer.data(), buffer.size());
if(ret != 0)
{
RETURN_ERROR_RESULT(ResultCode::ImageUnsupported,
"EXR file detected, but couldn't load with ParseEXRVersionFromMemory: %d",
ret);
}
if(exrVersion.multipart)
{
RETURN_ERROR_RESULT(ResultCode::ImageUnsupported,
"Unsupported EXR file detected - multipart EXR.");
}
if(exrVersion.non_image)
{
RETURN_ERROR_RESULT(ResultCode::ImageUnsupported,
"Unsupported EXR file detected - deep image EXR.");
}
// parse and check header
EXRHeader exrHeader;
InitEXRHeader(&exrHeader);
rdcstr errString;
{
const char *err = NULL;
ret = ParseEXRHeaderFromMemory(&exrHeader, &exrVersion, buffer.data(), buffer.size(), &err);
if(err)
{
errString = err;
free((void *)err);
}
}
if(ret != 0)
{
RETURN_ERROR_RESULT(
ResultCode::ImageUnsupported,
"EXR file detected, but couldn't load with ParseEXRHeaderFromMemory %d: '%s'", ret,
errString.c_str());
}
// load the file contents
for(int i = 0; i < exrHeader.num_channels; i++)
exrHeader.requested_pixel_types[i] = TINYEXR_PIXELTYPE_FLOAT;
EXRImage exrImage;
InitEXRImage(&exrImage);
{
const char *err = NULL;
ret = LoadEXRImageFromMemory(&exrImage, &exrHeader, buffer.data(), buffer.size(), &err);
if(err)
{
errString = err;
free((void *)err);
}
}
if(ret != 0)
{
RETURN_ERROR_RESULT(ResultCode::ImageUnsupported,
"EXR file detected, but couldn't load with LoadEXRImageFromMemory %d: '%s'",
ret, errString.c_str());
}
if(exrImage.width > 16384 || exrImage.height > 16384)
{
RETURN_ERROR_RESULT(ResultCode::OutOfMemory, "EXR dimension %d x %d is too large for display",
exrImage.width, exrImage.height);
}
// assemble texture data out of what tinyexr provides us
ResourceFormat rgba32_float;
rgba32_float.compByteWidth = 4;
rgba32_float.compCount = 4;
rgba32_float.compType = CompType::Float;
rgba32_float.type = ResourceFormatType::Regular;
read_data.width = exrImage.width;
read_data.height = exrImage.height;
read_data.format = rgba32_float;
read_data.depth = 1;
read_data.slices = 1;
read_data.mips = 1;
size_t totalSize = size_t(exrImage.width) * size_t(exrImage.height) * 4 * sizeof(float);
read_data.subresources.push_back({0, totalSize});
// Support mipmaps in EXR, if the tile rounding mode matches the graphics APIs (rounding
// down for odd sizes).
if(exrImage.images == NULL && exrImage.tiles != NULL &&
exrHeader.tile_level_mode == TINYEXR_TILE_MIPMAP_LEVELS &&
exrHeader.tile_rounding_mode == TINYEXR_TILE_ROUND_DOWN)
{
const EXRImage *exrLevel = exrImage.next_level;
while(exrLevel != NULL)
{
size_t mipSize = size_t(exrLevel->width) * size_t(exrLevel->height) * 4 * sizeof(float);
read_data.subresources.push_back({totalSize, mipSize});
totalSize += mipSize;
read_data.mips++;
exrLevel = exrLevel->next_level;
}
}
read_data.buffer.resize(totalSize);
// Expect R/G/B/A channel names as first char of the string, or
// after the last '.' char.
int channels[4] = {-1, -1, -1, -1};
for(int i = 0; i < exrImage.num_channels; i++)
{
const char *dotPos = strrchr(exrHeader.channels[i].name, '.');
const char *name = dotPos ? dotPos + 1 : exrHeader.channels[i].name;
switch(name[0])
{
case 'R': channels[0] = i; break;
case 'G': channels[1] = i; break;
case 'B': channels[2] = i; break;
case 'A': channels[3] = i; break;
}
}
if(exrImage.images != NULL)
{
// scanline image
const float **src = (const float **)exrImage.images;
const float *srcR = channels[0] >= 0 ? src[channels[0]] : NULL;
const float *srcG = channels[1] >= 0 ? src[channels[1]] : NULL;
const float *srcB = channels[2] >= 0 ? src[channels[2]] : NULL;
const float *srcA = channels[3] >= 0 ? src[channels[3]] : NULL;
float *rgba_dst = (float *)read_data.buffer.data();
for(uint32_t i = 0, n = exrImage.width * exrImage.height; i < n; i++)
{
rgba_dst[0] = srcR ? srcR[i] : 0.0f;
rgba_dst[1] = srcG ? srcG[i] : 0.0f;
rgba_dst[2] = srcB ? srcB[i] : 0.0f;
rgba_dst[3] = srcA ? srcA[i] : 1.0f;
rgba_dst += 4;
}
}
else if(exrImage.tiles != NULL)
{
// tiled image
const int fullTileWidth = exrHeader.tile_size_x;
const int fullTileHeight = exrHeader.tile_size_y;
const EXRImage *exrLevel = &exrImage;
for(uint32_t mip = 0; mip < read_data.mips; mip++, exrLevel = exrLevel->next_level)
{
for(int idx = 0; idx < exrLevel->num_tiles; idx++)
{
const EXRTile &tile = exrLevel->tiles[idx];
const int thisTileWidth = tile.width;
const int thisTileHeight = tile.height;
float **src = (float **)tile.images;
float *rgba_tile =
(float *)(read_data.buffer.data() + read_data.subresources[mip].first) +
(tile.offset_y * fullTileHeight * exrLevel->width + tile.offset_x * fullTileWidth) * 4;
for(int y = 0; y < thisTileHeight; y++)
{
const float *srcR = channels[0] >= 0 ? src[channels[0]] + y * fullTileWidth : NULL;
const float *srcG = channels[1] >= 0 ? src[channels[1]] + y * fullTileWidth : NULL;
const float *srcB = channels[2] >= 0 ? src[channels[2]] + y * fullTileWidth : NULL;
const float *srcA = channels[3] >= 0 ? src[channels[3]] + y * fullTileWidth : NULL;
float *rgba_dst = rgba_tile + y * exrLevel->width * 4;
for(int x = 0; x < thisTileWidth; x++)
{
rgba_dst[0] = srcR ? srcR[x] : 0.0f;
rgba_dst[1] = srcG ? srcG[x] : 0.0f;
rgba_dst[2] = srcB ? srcB[x] : 0.0f;
rgba_dst[3] = srcA ? srcA[x] : 1.0f;
rgba_dst += 4;
}
}
}
}
}
// cleanup
ret = FreeEXRImage(&exrImage);
if(ret != 0)
{
RETURN_ERROR_RESULT(ResultCode::ImageUnsupported,
"EXR file detected, but failed during parsing");
}
return ResultCode::Succeeded;
}
void ImageViewer::RefreshFile()
{
FILE *f = NULL;
@@ -637,7 +836,7 @@ void ImageViewer::RefreshFile()
size_t datasize = 0;
bool dds = false;
read_tex_data read_data = {};
bool exr = false;
byte headerBuffer[4];
const size_t headerSize = FileIO::fread(headerBuffer, 1, 4, f);
@@ -647,219 +846,7 @@ void ImageViewer::RefreshFile()
if(is_exr_file(headerBuffer, headerSize))
{
texDetails.format = rgba32_float;
FileIO::fseek64(f, 0, SEEK_SET);
bytebuf buffer;
buffer.resize((size_t)fileSize);
FileIO::fread(buffer.data(), 1, buffer.size(), f);
EXRVersion exrVersion;
int ret = ParseEXRVersionFromMemory(&exrVersion, buffer.data(), buffer.size());
if(ret != 0)
{
SET_ERROR_RESULT(m_Error, ResultCode::ImageUnsupported,
"EXR file detected, but couldn't load with ParseEXRVersionFromMemory: %d",
ret);
FileIO::fclose(f);
return;
}
if(exrVersion.multipart)
{
SET_ERROR_RESULT(m_Error, ResultCode::ImageUnsupported,
"Unsupported EXR file detected - multipart EXR.");
FileIO::fclose(f);
return;
}
if(exrVersion.non_image)
{
SET_ERROR_RESULT(m_Error, ResultCode::ImageUnsupported,
"Unsupported EXR file detected - deep image EXR.");
FileIO::fclose(f);
return;
}
EXRHeader exrHeader;
InitEXRHeader(&exrHeader);
rdcstr errString;
{
const char *err = NULL;
ret = ParseEXRHeaderFromMemory(&exrHeader, &exrVersion, buffer.data(), buffer.size(), &err);
if(err)
{
errString = err;
free((void *)err);
}
}
if(ret != 0)
{
SET_ERROR_RESULT(
m_Error, ResultCode::ImageUnsupported,
"EXR file detected, but couldn't load with ParseEXRHeaderFromMemory %d: '%s'", ret,
errString.c_str());
FileIO::fclose(f);
return;
}
for(int i = 0; i < exrHeader.num_channels; i++)
exrHeader.requested_pixel_types[i] = TINYEXR_PIXELTYPE_FLOAT;
EXRImage exrImage;
InitEXRImage(&exrImage);
{
const char *err = NULL;
ret = LoadEXRImageFromMemory(&exrImage, &exrHeader, buffer.data(), buffer.size(), &err);
if(err)
{
errString = err;
free((void *)err);
}
}
if(ret != 0)
{
SET_ERROR_RESULT(m_Error, ResultCode::ImageUnsupported,
"EXR file detected, but couldn't load with LoadEXRImageFromMemory %d: '%s'",
ret, errString.c_str());
FileIO::fclose(f);
return;
}
texDetails.width = exrImage.width;
texDetails.height = exrImage.height;
if(texDetails.width > 16384 || texDetails.height > 16384)
{
SET_ERROR_RESULT(m_Error, ResultCode::OutOfMemory,
"EXR dimension %d x %d is too large for display", exrImage.width,
exrImage.height);
return;
}
size_t totalSize = size_t(texDetails.width) * size_t(texDetails.height) * 4 * sizeof(float);
read_data.width = texDetails.width;
read_data.height = texDetails.height;
read_data.format = texDetails.format;
read_data.depth = 1;
read_data.slices = 1;
read_data.mips = 1;
read_data.subresources.push_back({0, totalSize});
// Support mipmaps in EXR, if the tile rounding mode matches the graphics APIs (rounding
// down for odd sizes).
if(exrImage.images == NULL && exrImage.tiles != NULL &&
exrHeader.tile_level_mode == TINYEXR_TILE_MIPMAP_LEVELS &&
exrHeader.tile_rounding_mode == TINYEXR_TILE_ROUND_DOWN)
{
const EXRImage *exrLevel = exrImage.next_level;
while(exrLevel != NULL)
{
size_t mipSize = size_t(exrLevel->width) * size_t(exrLevel->height) * 4 * sizeof(float);
read_data.subresources.push_back({totalSize, mipSize});
totalSize += mipSize;
read_data.mips++;
exrLevel = exrLevel->next_level;
}
}
texDetails.mips = read_data.mips;
read_data.buffer.resize(totalSize);
// Expect R/G/B/A channel names as first char of the string, or
// after the last '.' char.
int channels[4] = {-1, -1, -1, -1};
for(int i = 0; i < exrImage.num_channels; i++)
{
const char *dotPos = strrchr(exrHeader.channels[i].name, '.');
const char *name = dotPos ? dotPos + 1 : exrHeader.channels[i].name;
switch(name[0])
{
case 'R': channels[0] = i; break;
case 'G': channels[1] = i; break;
case 'B': channels[2] = i; break;
case 'A': channels[3] = i; break;
}
}
if(exrImage.images != NULL)
{
// scanline image
const float **src = (const float **)exrImage.images;
const float *srcR = channels[0] >= 0 ? src[channels[0]] : NULL;
const float *srcG = channels[1] >= 0 ? src[channels[1]] : NULL;
const float *srcB = channels[2] >= 0 ? src[channels[2]] : NULL;
const float *srcA = channels[3] >= 0 ? src[channels[3]] : NULL;
float *rgba_dst = (float *)read_data.buffer.data();
for(uint32_t i = 0, n = texDetails.width * texDetails.height; i < n; i++)
{
rgba_dst[0] = srcR ? srcR[i] : 0.0f;
rgba_dst[1] = srcG ? srcG[i] : 0.0f;
rgba_dst[2] = srcB ? srcB[i] : 0.0f;
rgba_dst[3] = srcA ? srcA[i] : 1.0f;
rgba_dst += 4;
}
}
else if(exrImage.tiles != NULL)
{
// tiled image
const int fullTileWidth = exrHeader.tile_size_x;
const int fullTileHeight = exrHeader.tile_size_y;
const EXRImage *exrLevel = &exrImage;
for(uint32_t mip = 0; mip < read_data.mips; mip++, exrLevel = exrLevel->next_level)
{
for(int idx = 0; idx < exrLevel->num_tiles; idx++)
{
const EXRTile &tile = exrLevel->tiles[idx];
const int thisTileWidth = tile.width;
const int thisTileHeight = tile.height;
float **src = (float **)tile.images;
float *rgba_tile =
(float *)(read_data.buffer.data() + read_data.subresources[mip].first) +
(tile.offset_y * fullTileHeight * exrLevel->width + tile.offset_x * fullTileWidth) * 4;
for(int y = 0; y < thisTileHeight; y++)
{
const float *srcR = channels[0] >= 0 ? src[channels[0]] + y * fullTileWidth : NULL;
const float *srcG = channels[1] >= 0 ? src[channels[1]] + y * fullTileWidth : NULL;
const float *srcB = channels[2] >= 0 ? src[channels[2]] + y * fullTileWidth : NULL;
const float *srcA = channels[3] >= 0 ? src[channels[3]] + y * fullTileWidth : NULL;
float *rgba_dst = rgba_tile + y * exrLevel->width * 4;
for(int x = 0; x < thisTileWidth; x++)
{
rgba_dst[0] = srcR ? srcR[x] : 0.0f;
rgba_dst[1] = srcG ? srcG[x] : 0.0f;
rgba_dst[2] = srcB ? srcB[x] : 0.0f;
rgba_dst[3] = srcA ? srcA[x] : 1.0f;
rgba_dst += 4;
}
}
}
}
}
ret = FreeEXRImage(&exrImage);
// shouldn't get here but let's be safe
if(ret != 0)
{
SET_ERROR_RESULT(m_Error, ResultCode::ImageUnsupported,
"EXR file detected, but failed during parsing");
FileIO::fclose(f);
return;
}
exr = true;
}
else if(stbi_is_hdr_from_file(f))
{
@@ -908,9 +895,9 @@ void ImageViewer::RefreshFile()
datasize = texDetails.width * texDetails.height * 4 * sizeof(byte);
}
// if we don't have data at this point (and we're not a dds file) then the
// if we don't have data at this point (and we're not a dds/exr file) then the
// file was corrupted and we failed to load it
if(!dds && data == NULL && read_data.buffer.isEmpty())
if(!dds && !exr && data == NULL)
{
SET_ERROR_RESULT(m_Error, ResultCode::ImageUnsupported, "Image failed to load");
FileIO::fclose(f);
@@ -919,14 +906,24 @@ void ImageViewer::RefreshFile()
m_FrameRecord.frameInfo.initDataSize = 0;
m_FrameRecord.frameInfo.persistentSize = 0;
m_FrameRecord.frameInfo.uncompressedFileSize = RDCMAX(datasize, read_data.buffer.size());
m_FrameRecord.frameInfo.uncompressedFileSize = datasize;
if(dds)
read_tex_data read_data = {};
if(dds || exr)
{
FileIO::fseek64(f, 0, SEEK_SET);
StreamReader reader(f);
RDResult res = load_dds_from_file(&reader, read_data);
f = NULL;
RDResult res = ResultCode::FileIOFailed;
if(dds)
{
StreamReader reader(f);
res = load_dds_from_file(&reader, read_data);
f = NULL;
}
else if(exr)
{
res = load_exr_from_file(f, fileSize, read_data);
}
if(res != ResultCode::Succeeded)
{