From 8c935fcc010f14014257cc61614ef3b3d965dfe9 Mon Sep 17 00:00:00 2001 From: Alexander Lobakin Date: Thu, 26 Feb 2026 21:20:30 +0100 Subject: [PATCH] Add LoadFromDDSStream{,Ex}() Many games support own archive formats and implement own sort of a virtual file system. In that case, LoadFromDDSFile() doesn't fit, while LoadFromDDSMemory() requires 2x filesize: one buffer to read the file and another one to load the texture, since ScratchImage is always owning. Add a simple InputStream class and LoadDDSFromStream() to avoid this memory overhead and allow games and apps to use their own functionality to operate with files or memory. More formats could support this in future, as well as writing to an OutputStream if/when needed. Signed-off-by: Alexander Lobakin --- Auxiliary/DirectXTexEXR.cpp | 30 +++--- DirectXTex/DirectXTex.h | 22 +++++ DirectXTex/DirectXTexDDS.cpp | 184 +++++++++++++++++++++++++++++++++++ 3 files changed, 221 insertions(+), 15 deletions(-) diff --git a/Auxiliary/DirectXTexEXR.cpp b/Auxiliary/DirectXTexEXR.cpp index 94070a4d..b34ca86f 100644 --- a/Auxiliary/DirectXTexEXR.cpp +++ b/Auxiliary/DirectXTexEXR.cpp @@ -82,10 +82,10 @@ namespace HRESULT result; }; - class InputStream : public Imf::IStream + class EXRInputStream : public Imf::IStream { public: - InputStream(HANDLE hFile, const char fileName[]) : + EXRInputStream(HANDLE hFile, const char fileName[]) : IStream(fileName), m_hFile(hFile) { const LARGE_INTEGER dist = {}; @@ -103,11 +103,11 @@ namespace } } - InputStream(const InputStream&) = delete; - InputStream& operator = (const InputStream&) = delete; + EXRInputStream(const EXRInputStream&) = delete; + EXRInputStream& operator = (const EXRInputStream&) = delete; - InputStream(InputStream&&) = delete; - InputStream& operator=(InputStream&&) = delete; + EXRInputStream(EXRInputStream&&) = delete; + EXRInputStream& operator=(EXRInputStream&&) = delete; bool read(char c[], int n) override { @@ -165,18 +165,18 @@ namespace LONGLONG m_EOF; }; - class OutputStream : public Imf::OStream + class EXROutputStream : public Imf::OStream { public: - OutputStream(HANDLE hFile, const char fileName[]) : + EXROutputStream(HANDLE hFile, const char fileName[]) : OStream(fileName), m_hFile(hFile) {} - OutputStream(const OutputStream&) = delete; - OutputStream& operator = (const OutputStream&) = delete; + EXROutputStream(const EXROutputStream&) = delete; + EXROutputStream& operator = (const EXROutputStream&) = delete; - OutputStream(OutputStream&&) = delete; - OutputStream& operator=(OutputStream&&) = delete; + EXROutputStream(EXROutputStream&&) = delete; + EXROutputStream& operator=(EXROutputStream&&) = delete; void write(const char c[], int n) override { @@ -250,7 +250,7 @@ HRESULT DirectX::GetMetadataFromEXRFile(const wchar_t* szFile, TexMetadata& meta return HRESULT_FROM_WIN32(GetLastError()); } - InputStream stream(hFile.get(), fileName.c_str()); + EXRInputStream stream(hFile.get(), fileName.c_str()); #else std::wstring wFileName(szFile); std::string fileName(wFileName.cbegin(), wFileName.cend()); @@ -359,7 +359,7 @@ HRESULT DirectX::LoadFromEXRFile(const wchar_t* szFile, TexMetadata* metadata, S return HRESULT_FROM_WIN32(GetLastError()); } - InputStream stream(hFile.get(), fileName.c_str()); + EXRInputStream stream(hFile.get(), fileName.c_str()); #else std::wstring wFileName(szFile); std::string fileName(wFileName.cbegin(), wFileName.cend()); @@ -502,7 +502,7 @@ HRESULT DirectX::SaveToEXRFile(const Image& image, const wchar_t* szFile) auto_delete_file delonfail(hFile.get()); - OutputStream stream(hFile.get(), fileName.c_str()); + EXROutputStream stream(hFile.get(), fileName.c_str()); #else std::wstring wFileName(szFile); std::string fileName(wFileName.cbegin(), wFileName.cend()); diff --git a/DirectXTex/DirectXTex.h b/DirectXTex/DirectXTex.h index b3e4a7a4..21d2c3c2 100644 --- a/DirectXTex/DirectXTex.h +++ b/DirectXTex/DirectXTex.h @@ -576,11 +576,27 @@ namespace DirectX //--------------------------------------------------------------------------------- // Image I/O + class DIRECTX_TEX_API InputStream + { + public: + virtual ~InputStream() = default; + + virtual bool Read(_Out_writes_bytes_(size) void* data, _In_ size_t size) = 0; + + virtual bool Seek(_In_ size_t position) = 0; + + virtual size_t Size() = 0; + }; + // DDS operations DIRECTX_TEX_API HRESULT __cdecl LoadFromDDSMemory( _In_reads_bytes_(size) const uint8_t* pSource, _In_ size_t size, _In_ DDS_FLAGS flags, _Out_opt_ TexMetadata* metadata, _Out_ ScratchImage& image) noexcept; + DIRECTX_TEX_API HRESULT __cdecl LoadFromDDSStream( + _In_ InputStream& stream, + _In_ DDS_FLAGS flags, + _Out_opt_ TexMetadata* metadata, _Out_ ScratchImage& image) noexcept; DIRECTX_TEX_API HRESULT __cdecl LoadFromDDSFile( _In_z_ const wchar_t* szFile, _In_ DDS_FLAGS flags, @@ -592,6 +608,12 @@ namespace DirectX _Out_opt_ TexMetadata* metadata, _Out_opt_ DDSMetaData* ddPixelFormat, _Out_ ScratchImage& image) noexcept; + DIRECTX_TEX_API HRESULT __cdecl LoadFromDDSStreamEx( + _In_ InputStream& stream, + _In_ DDS_FLAGS flags, + _Out_opt_ TexMetadata* metadata, + _Out_opt_ DDSMetaData* ddPixelFormat, + _Out_ ScratchImage& image) noexcept; DIRECTX_TEX_API HRESULT __cdecl LoadFromDDSFileEx( _In_z_ const wchar_t* szFile, _In_ DDS_FLAGS flags, diff --git a/DirectXTex/DirectXTexDDS.cpp b/DirectXTex/DirectXTexDDS.cpp index 48a2a6d0..69f5ef28 100644 --- a/DirectXTex/DirectXTexDDS.cpp +++ b/DirectXTex/DirectXTexDDS.cpp @@ -2112,6 +2112,190 @@ HRESULT DirectX::LoadFromDDSMemoryEx( } +//------------------------------------------------------------------------------------- +// Load a DDS file from stream +//------------------------------------------------------------------------------------- +_Use_decl_annotations_ +HRESULT DirectX::LoadFromDDSStream( + InputStream& stream, + DDS_FLAGS flags, + TexMetadata* metadata, + ScratchImage& image) noexcept +{ + return LoadFromDDSStreamEx(stream, flags, metadata, nullptr, image); +} + +_Use_decl_annotations_ +HRESULT DirectX::LoadFromDDSStreamEx( + InputStream& stream, + DDS_FLAGS flags, + TexMetadata* metadata, + DDSMetaData* ddPixelFormat, + ScratchImage& image) noexcept +{ + image.Release(); + + size_t fileLen = stream.Size(); + if (fileLen == 0) + return E_FAIL; + + if (fileLen > UINT32_MAX) + return HRESULT_E_FILE_TOO_LARGE; + + const size_t len = fileLen; + + // Need at least enough data to fill the standard header and magic number to be a valid DDS + if (len < DDS_MIN_HEADER_SIZE) + { + return E_FAIL; + } + + // Read the header in (including extended header if present) + uint8_t header[DDS_DX10_HEADER_SIZE] = {}; + + const auto headerLen = std::min(len, DDS_DX10_HEADER_SIZE); + + if (!stream.Read(header, headerLen)) + return E_FAIL; + + uint32_t convFlags = 0; + TexMetadata mdata; + HRESULT hr = DecodeDDSHeader(header, headerLen, flags, mdata, ddPixelFormat, convFlags); + if (FAILED(hr)) + return hr; + + size_t offset = DDS_DX10_HEADER_SIZE; + + if (!(convFlags & CONV_FLAGS_DX10)) + { + if (!stream.Seek(DDS_MIN_HEADER_SIZE)) + return E_FAIL; + + offset = DDS_MIN_HEADER_SIZE; + } + + std::unique_ptr pal8; + if (convFlags & CONV_FLAGS_PAL8) + { + pal8.reset(new (std::nothrow) uint32_t[256]); + if (!pal8) + { + return E_OUTOFMEMORY; + } + + if (!stream.Read(pal8.get(), 256 * sizeof(uint32_t))) + return E_FAIL; + + offset += (256 * sizeof(uint32_t)); + } + + const size_t remaining = len - offset; + if (remaining == 0) + return E_FAIL; + + hr = image.Initialize(mdata); + if (FAILED(hr)) + return hr; + + if (flags & DDS_FLAGS_PERMISSIVE) + { + // For cubemaps, DDS_HEADER_DXT10.arraySize is supposed to be 'number of cubes'. + // This handles cases where the value is incorrectly written as the original 6*numCubes value. + if ((mdata.miscFlags & TEX_MISC_TEXTURECUBE) + && (convFlags & CONV_FLAGS_DX10) + && (image.GetPixelsSize() > remaining) + && ((mdata.arraySize % 6) == 0)) + { + mdata.arraySize = mdata.arraySize / 6; + hr = image.Initialize(mdata); + if (FAILED(hr)) + return hr; + + if (image.GetPixelsSize() > remaining) + { + image.Release(); + return HRESULT_E_HANDLE_EOF; + } + } + } + + if ((convFlags & CONV_FLAGS_EXPAND) || (flags & (DDS_FLAGS_LEGACY_DWORD | DDS_FLAGS_BAD_DXTN_TAILS))) + { + std::unique_ptr temp(new (std::nothrow) uint8_t[remaining]); + if (!temp) + { + image.Release(); + return E_OUTOFMEMORY; + } + + if (!stream.Read(temp.get(), remaining)) + { + image.Release(); + return E_FAIL; + } + + CP_FLAGS cflags = CP_FLAGS_NONE; + if (flags & DDS_FLAGS_LEGACY_DWORD) + { + cflags |= CP_FLAGS_LEGACY_DWORD; + } + if (flags & DDS_FLAGS_BAD_DXTN_TAILS) + { + cflags |= CP_FLAGS_BAD_DXTN_TAILS; + } + + hr = CopyImage(temp.get(), + remaining, + mdata, + cflags, + convFlags, + pal8.get(), + image); + if (FAILED(hr)) + { + image.Release(); + return hr; + } + } + else + { + if (remaining < image.GetPixelsSize()) + { + image.Release(); + return HRESULT_E_HANDLE_EOF; + } + + if (image.GetPixelsSize() > UINT32_MAX) + { + image.Release(); + return HRESULT_E_ARITHMETIC_OVERFLOW; + } + + if (!stream.Read(image.GetPixels(), image.GetPixelsSize())) + { + image.Release(); + return E_FAIL; + } + + if (convFlags & (CONV_FLAGS_SWIZZLE | CONV_FLAGS_NOALPHA | CONV_FLAGS_L8U8V8 | CONV_FLAGS_WUV10)) + { + // Swizzle/copy image in place + hr = CopyImageInPlace(convFlags, image); + if (FAILED(hr)) + { + image.Release(); + return hr; + } + } + } + + if (metadata) + memcpy(metadata, &mdata, sizeof(TexMetadata)); + + return S_OK; +} + + //------------------------------------------------------------------------------------- // Load a DDS file from disk //-------------------------------------------------------------------------------------