mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-05-04 17:10:47 +00:00
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:
committed by
Baldur Karlsson
parent
02fd7ac0cd
commit
0193703037
+218
-221
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user