1
0
forked from 0ad/0ad

Allows tex clients decide how to handle invalid textures instead of assertions. Fixes #6436

This was SVN commit r26783.
This commit is contained in:
Vladislav Belov 2022-04-12 17:39:05 +00:00
parent 8c1a469253
commit d8d736f0eb
10 changed files with 72 additions and 42 deletions

View File

@ -29,6 +29,7 @@
#include "ps/CStr.h"
#include "ps/Profiler2.h"
#include "ps/Threading.h"
#include "ps/Util.h"
#include "ps/XML/Xeromyces.h"
#if CONFIG2_NVTT
@ -342,9 +343,10 @@ bool CTextureConverter::ConvertTexture(const CTexturePtr& texture, const VfsPath
}
Tex tex;
if (tex.decode(file, fileSize) < 0)
const Status decodeStatus = tex.decode(file, fileSize);
if (decodeStatus != INFO::OK)
{
LOGERROR("Failed to decode texture \"%s\"", src.string8());
LOGERROR("Failed to decode texture \"%s\" %s", src.string8(), GetStatusAsString(decodeStatus).c_str());
return false;
}

View File

@ -33,6 +33,7 @@
#include "ps/ConfigDB.h"
#include "ps/Filesystem.h"
#include "ps/Profile.h"
#include "ps/Util.h"
#include "ps/VideoMode.h"
#include "renderer/backend/gl/Device.h"
#include "renderer/Renderer.h"
@ -401,12 +402,24 @@ public:
std::shared_ptr<u8> fileData;
size_t fileSize;
const Status loadStatus = g_VFS->LoadFile(path, fileData, fileSize);
if (loadStatus != INFO::OK)
{
LOGERROR("Texture failed to load; \"%s\" %s",
texture->m_Properties.m_Path.string8(), GetStatusAsString(loadStatus).c_str());
texture->ResetBackendTexture(
nullptr, m_ErrorTexture.GetTexture()->GetBackendTexture());
texture->m_TextureData.reset();
return;
}
texture->m_TextureData = std::make_unique<Tex>();
Tex& textureData = *texture->m_TextureData;
if (g_VFS->LoadFile(path, fileData, fileSize) != INFO::OK ||
textureData.decode(fileData, fileSize) != INFO::OK)
const Status decodeStatus = textureData.decode(fileData, fileSize);
if (decodeStatus != INFO::OK)
{
LOGERROR("Texture failed to load; \"%s\"", texture->m_Properties.m_Path.string8());
LOGERROR("Texture failed to decode; \"%s\" %s",
texture->m_Properties.m_Path.string8(), GetStatusAsString(decodeStatus).c_str());
texture->ResetBackendTexture(
nullptr, m_ErrorTexture.GetTexture()->GetBackendTexture());
texture->m_TextureData.reset();

View File

@ -34,7 +34,9 @@
#include <cmath>
#include <cstdlib>
static const StatusDefinition texStatusDefinitions[] = {
static const StatusDefinition texStatusDefinitions[] =
{
{ ERR::TEX_UNKNOWN_FORMAT, L"Unknown texture format" },
{ ERR::TEX_FMT_INVALID, L"Invalid/unsupported texture format" },
{ ERR::TEX_INVALID_COLOR_TYPE, L"Invalid color type" },
{ ERR::TEX_NOT_8BIT_PRECISION, L"Not 8-bit channel precision" },
@ -55,7 +57,7 @@ STATUS_ADD_DEFINITIONS(texStatusDefinitions);
Status Tex::validate() const
{
if(m_Flags & TEX_UNDEFINED_FLAGS)
WARN_RETURN(ERR::_1);
return ERR::_1;
// pixel data (only check validity if the image is still in memory).
if(m_Data)
@ -64,23 +66,23 @@ Status Tex::validate() const
// possible causes: texture file header is invalid,
// or file wasn't loaded completely.
if(m_DataSize < m_Ofs + m_Width*m_Height*m_Bpp/8)
WARN_RETURN(ERR::_2);
return ERR::_2;
}
// bits per pixel
// (we don't bother checking all values; a sanity check is enough)
if(m_Bpp % 4 || m_Bpp > 32)
WARN_RETURN(ERR::_3);
return ERR::_3;
// flags
// .. DXT value
const size_t dxt = m_Flags & TEX_DXT;
if(dxt != 0 && dxt != 1 && dxt != DXT1A && dxt != 3 && dxt != 5)
WARN_RETURN(ERR::_4);
return ERR::_4;
// .. orientation
const size_t orientation = m_Flags & TEX_ORIENTATION;
if(orientation == (TEX_BOTTOM_UP|TEX_TOP_DOWN))
WARN_RETURN(ERR::_5);
return ERR::_5;
return INFO::OK;
}
@ -718,10 +720,10 @@ Status Tex::decode(const std::shared_ptr<u8>& Data, size_t DataSize)
// make sure the entire header is available
const size_t min_hdr_size = c->hdr_size(0);
if(DataSize < min_hdr_size)
WARN_RETURN(ERR::TEX_INCOMPLETE_HEADER);
return ERR::TEX_INCOMPLETE_HEADER;
const size_t hdr_size = c->hdr_size(Data.get());
if(DataSize < hdr_size)
WARN_RETURN(ERR::TEX_INCOMPLETE_HEADER);
return ERR::TEX_INCOMPLETE_HEADER;
m_Data = Data;
m_DataSize = DataSize;
@ -731,9 +733,9 @@ Status Tex::decode(const std::shared_ptr<u8>& Data, size_t DataSize)
// sanity checks
if(!m_Width || !m_Height || m_Bpp > 32)
WARN_RETURN(ERR::TEX_FMT_INVALID);
return ERR::TEX_FMT_INVALID;
if(m_DataSize < m_Ofs + img_size())
WARN_RETURN(ERR::TEX_INVALID_SIZE);
return ERR::TEX_INVALID_SIZE;
flip_to_global_orientation(this);

View File

@ -67,7 +67,7 @@ Status tex_codec_for_header(const u8* file, size_t file_size, const ITexCodec**
{
// we guarantee at least 4 bytes for is_hdr to look at
if(file_size < 4)
WARN_RETURN(ERR::TEX_INCOMPLETE_HEADER);
return ERR::TEX_INCOMPLETE_HEADER;
for(int i = 0; i < codecs_len; ++i)
{
@ -78,7 +78,7 @@ Status tex_codec_for_header(const u8* file, size_t file_size, const ITexCodec**
}
}
WARN_RETURN(ERR::TEX_UNKNOWN_FORMAT);
return ERR::TEX_UNKNOWN_FORMAT;
}
Status tex_codec_transform(Tex* t, size_t transforms)

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2020 Wildfire Games.
/* Copyright (C) 2022 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -397,7 +397,7 @@ static Status decode_pf(const DDS_PIXELFORMAT* pf, size_t& bpp, size_t& flags)
// check struct size
if(read_le32(&pf->dwSize) != sizeof(DDS_PIXELFORMAT))
WARN_RETURN(ERR::TEX_INVALID_SIZE);
return ERR::TEX_INVALID_SIZE;
// determine type
const size_t pf_flags = (size_t)read_le32(&pf->dwFlags);
@ -432,7 +432,7 @@ static Status decode_pf(const DDS_PIXELFORMAT* pf, size_t& bpp, size_t& flags)
// DDS is storing images in a format that requires no processing,
// we do not allow any weird orderings that require runtime work.
// instead, the artists must export with the correct settings.
WARN_RETURN(ERR::TEX_FMT_INVALID);
return ERR::TEX_FMT_INVALID;
}
RETURN_STATUS_IF_ERR(tex_validate_plain_format(bpp, (int)flags));
@ -446,10 +446,10 @@ static Status decode_pf(const DDS_PIXELFORMAT* pf, size_t& bpp, size_t& flags)
bpp = pf_bpp;
if(pf_bpp != 8)
WARN_RETURN(ERR::TEX_FMT_INVALID);
return ERR::TEX_FMT_INVALID;
if(pf_a_mask != 0xFF)
WARN_RETURN(ERR::TEX_FMT_INVALID);
return ERR::TEX_FMT_INVALID;
flags |= TEX_GREY;
RETURN_STATUS_IF_ERR(tex_validate_plain_format(bpp, (int)flags));
@ -480,12 +480,12 @@ static Status decode_pf(const DDS_PIXELFORMAT* pf, size_t& bpp, size_t& flags)
break;
default:
WARN_RETURN(ERR::TEX_FMT_INVALID);
return ERR::TEX_FMT_INVALID;
}
}
// .. neither uncompressed nor compressed - invalid
else
WARN_RETURN(ERR::TEX_FMT_INVALID);
return ERR::TEX_FMT_INVALID;
return INFO::OK;
}
@ -499,7 +499,7 @@ static Status decode_sd(const DDS_HEADER* sd, size_t& w, size_t& h, size_t& bpp,
{
// check header size
if(read_le32(&sd->dwSize) != sizeof(*sd))
WARN_RETURN(ERR::CORRUPTED);
return ERR::CORRUPTED;
// flags (indicate which fields are valid)
const size_t sd_flags = (size_t)read_le32(&sd->dwFlags);
@ -507,7 +507,7 @@ static Status decode_sd(const DDS_HEADER* sd, size_t& w, size_t& h, size_t& bpp,
// note: we can't guess dimensions - the image may not be square.
const size_t sd_req_flags = DDSD_CAPS|DDSD_HEIGHT|DDSD_WIDTH|DDSD_PIXELFORMAT;
if((sd_flags & sd_req_flags) != sd_req_flags)
WARN_RETURN(ERR::TEX_INCOMPLETE_HEADER);
return ERR::TEX_INCOMPLETE_HEADER;
// image dimensions
h = (size_t)read_le32(&sd->dwHeight);
@ -537,7 +537,7 @@ static Status decode_sd(const DDS_HEADER* sd, size_t& w, size_t& h, size_t& bpp,
if(sd_flags & DDSD_PITCH)
{
if(sd_pitch_or_size != Align<4>(pitch))
DEBUG_WARN_ERR(ERR::CORRUPTED);
return ERR::CORRUPTED;
}
if(sd_flags & DDSD_LINEARSIZE)
{
@ -545,7 +545,7 @@ static Status decode_sd(const DDS_HEADER* sd, size_t& w, size_t& h, size_t& bpp,
// so allow values close to that as well
const ssize_t totalSize = ssize_t(pitch*stored_h*1.333333f);
if(sd_pitch_or_size != pitch*stored_h && std::abs(ssize_t(sd_pitch_or_size)-totalSize) > 64)
DEBUG_WARN_ERR(ERR::CORRUPTED);
return ERR::CORRUPTED;
}
// note: both flags set would be invalid; no need to check for that,
// though, since one of the above tests would fail.
@ -559,7 +559,7 @@ static Status decode_sd(const DDS_HEADER* sd, size_t& w, size_t& h, size_t& bpp,
// mipmap chain is incomplete
// note: DDS includes the base level in its count, hence +1.
if(mipmap_count != ceil_log2(std::max(w,h))+1)
WARN_RETURN(ERR::TEX_FMT_INVALID);
return ERR::TEX_FMT_INVALID;
flags |= TEX_MIPMAPS;
}
}
@ -569,17 +569,19 @@ static Status decode_sd(const DDS_HEADER* sd, size_t& w, size_t& h, size_t& bpp,
{
const size_t depth = (size_t)read_le32(&sd->dwDepth);
if(depth)
WARN_RETURN(ERR::NOT_SUPPORTED);
return ERR::NOT_SUPPORTED;
}
// check caps
// .. this is supposed to be set, but don't bail if not (pointless)
ENSURE(sd->dwCaps & DDSCAPS_TEXTURE);
if (!(sd->dwCaps & DDSCAPS_TEXTURE))
return ERR::CORRUPTED;
// .. sanity check: warn if mipmap flag not set (don't bail if not
// because we've already made the decision).
const bool mipmap_cap = (sd->dwCaps & DDSCAPS_MIPMAP) != 0;
const bool mipmap_flag = (flags & TEX_MIPMAPS) != 0;
ENSURE(mipmap_cap == mipmap_flag);
if (mipmap_cap != mipmap_flag)
return ERR::CORRUPTED;
// note: we do not check for cubemaps and volume textures (not supported)
// because the file may still have useful data we can read.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2018 Wildfire Games.
/* Copyright (C) 2022 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
@ -271,7 +271,7 @@ TIMER_ADD_CLIENT(tc_png_decode);
// limitation: palette images aren't supported
Status TexCodecPng::decode(u8* RESTRICT data, size_t size, Tex* RESTRICT t) const
{
TIMER_ACCRUE(tc_png_decode);
TIMER_ACCRUE(tc_png_decode);
png_infop info_ptr = 0;
@ -279,19 +279,19 @@ TIMER_ACCRUE(tc_png_decode);
// warning handler to filter out useless messages
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, user_warning_fn);
if(!png_ptr)
WARN_RETURN(ERR::FAIL);
return ERR::FAIL;
info_ptr = png_create_info_struct(png_ptr);
if(!info_ptr)
{
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
WARN_RETURN(ERR::NO_MEM);
return ERR::NO_MEM;
}
// setup error handling
if(setjmp(png_jmpbuf(png_ptr)))
{
// libpng longjmps here after an error
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
WARN_RETURN(ERR::FAIL);
return ERR::FAIL;
}
MemoryStream stream(data, size);

View File

@ -144,7 +144,10 @@ const wchar_t* ErrorString(int err)
// TODO: load from language file
}
CStr GetStatusAsString(Status status)
{
return utf8_from_wstring(ErrorString(status));
}
// write the specified texture to disk.
// note: <t> cannot be made const because the image may have to be

View File

@ -18,8 +18,10 @@
#ifndef PS_UTIL_H
#define PS_UTIL_H
#include "lib/os_path.h"
#include "lib/file/vfs/vfs_path.h"
#include "lib/os_path.h"
#include "lib/status.h"
#include "ps/CStr.h"
class Tex;
@ -27,6 +29,8 @@ void WriteSystemInfo();
const wchar_t* ErrorString(int err);
CStr GetStatusAsString(Status status);
OsPath createDateIndexSubdirectory(const OsPath& parentDir);
Status tex_write(Tex* t, const VfsPath& filename);

View File

@ -95,8 +95,12 @@ void SkyManager::LoadAndUploadSkyTexturesIfNeeded(
}
}
textures[i].decode(file, fileSize);
textures[i].transform_to((textures[i].m_Flags | TEX_BOTTOM_UP | TEX_ALPHA) & ~(TEX_DXT | TEX_MIPMAPS));
if (textures[i].decode(file, fileSize) != INFO::OK ||
textures[i].transform_to((textures[i].m_Flags | TEX_BOTTOM_UP | TEX_ALPHA) & ~(TEX_DXT | TEX_MIPMAPS)) != INFO::OK)
{
LOGERROR("Error creating sky cubemap '%s', can't decode file: '%s'.", m_SkySet.ToUTF8().c_str(), path.string8().c_str());
return;
}
if (!is_pow2(textures[i].m_Width) || !is_pow2(textures[i].m_Height))
{

View File

@ -253,7 +253,7 @@ static void HandleError(const std::wstring& message, const VfsPath& pathname, St
if (err == ERR::AGAIN)
return;
LOGERROR("%s: pathname=%s, error=%s", utf8_from_wstring(message), pathname.string8(), utf8_from_wstring(ErrorString(err)));
LOGERROR("%s: pathname=%s, error=%s", utf8_from_wstring(message), pathname.string8(), GetStatusAsString(err).c_str());
}
void CSoundGroup::PlayNext(const CVector3D& position, entity_id_t source)