diff --git a/src/Common/Game/IW3/IW3_Assets.h b/src/Common/Game/IW3/IW3_Assets.h index e1a9ba2d..de2939d9 100644 --- a/src/Common/Game/IW3/IW3_Assets.h +++ b/src/Common/Game/IW3/IW3_Assets.h @@ -787,6 +787,19 @@ namespace IW3 TS_WATER_MAP = 0xB, }; + enum ImageCategory + { + IMG_CATEGORY_UNKNOWN = 0x0, + IMG_CATEGORY_AUTO_GENERATED = 0x1, + IMG_CATEGORY_LIGHTMAP = 0x2, + IMG_CATEGORY_LOAD_FROM_FILE = 0x3, + IMG_CATEGORY_RAW = 0x4, + IMG_CATEGORY_FIRST_UNMANAGED = 0x5, + IMG_CATEGORY_WATER = 0x5, + IMG_CATEGORY_RENDERTARGET = 0x6, + IMG_CATEGORY_TEMP = 0x7, + }; + struct GfxImage { MapType mapType; diff --git a/src/ObjLoading/Game/IW3/AssetLoaders/AssetLoaderGfxImage.cpp b/src/ObjLoading/Game/IW3/AssetLoaders/AssetLoaderGfxImage.cpp new file mode 100644 index 00000000..f1d08945 --- /dev/null +++ b/src/ObjLoading/Game/IW3/AssetLoaders/AssetLoaderGfxImage.cpp @@ -0,0 +1,132 @@ +#include "AssetLoaderGfxImage.h" + +#include +#include + +#include "Game/IW3/IW3.h" +#include "Image/DdsLoader.h" +#include "Pool/GlobalAssetPool.h" +#include "Image/IwiTypes.h" + +using namespace IW3; + +void* AssetLoaderGfxImage::CreateEmptyAsset(const std::string& assetName, MemoryManager* memory) +{ + auto* image = memory->Create(); + memset(image, 0, sizeof(GfxImage)); + image->name = memory->Dup(assetName.c_str()); + return image; +} + +bool AssetLoaderGfxImage::CanLoadFromRaw() const +{ + return true; +} + +bool AssetLoaderGfxImage::LoadFromRaw(const std::string& assetName, ISearchPath* searchPath, MemoryManager* memory, IAssetLoadingManager* manager, Zone* zone) const +{ + // Do not load any GfxImages from raw for now that are not loaded + // TODO: Load iwis and add streaming info to asset + if (assetName.empty() || assetName[0] != '*') + return false; + + std::string safeAssetName = assetName; + for (auto& c : safeAssetName) + { + switch (c) + { + case '*': + c = '_'; + break; + + default: + break; + } + } + + const auto file = searchPath->Open("images/" + safeAssetName + ".dds"); + if (!file.IsOpen()) + return false; + + const DdsLoader ddsLoader(zone->GetMemory()); + auto* texture = ddsLoader.LoadDds(*file.m_stream); + + if (texture == nullptr) + { + std::cout << "Failed to load dds file for image asset \"" << assetName << "\"" << std::endl; + return false; + } + + auto* image = memory->Create(); + image->name = memory->Dup(assetName.c_str()); + image->picmip.platform[0] = 0; + image->picmip.platform[1] = 0; + image->noPicmip = !texture->HasMipMaps(); + image->semantic = TS_FUNCTION; + image->track = 0; + image->cardMemory.platform[0] = 0; + image->cardMemory.platform[1] = 0; + image->width = static_cast(texture->GetWidth()); + image->height = static_cast(texture->GetHeight()); + image->depth = static_cast(texture->GetDepth()); + image->category = IMG_CATEGORY_AUTO_GENERATED; + image->delayLoadPixels = false; + + switch (texture->GetTextureType()) + { + case TextureType::T_2D: + image->mapType = MAPTYPE_2D; + break; + + case TextureType::T_3D: + image->mapType = MAPTYPE_3D; + break; + + case TextureType::T_CUBE: + image->mapType = MAPTYPE_CUBE; + break; + + default: + image->mapType = MAPTYPE_NONE; + break; + } + + const auto mipCount = texture->HasMipMaps() ? texture->GetMipMapCount() : 1; + const auto faceCount = texture->GetFaceCount(); + + size_t dataSize = 0; + for (auto mipLevel = 0u; mipLevel < mipCount; mipLevel++) + dataSize += texture->GetSizeOfMipLevel(mipLevel) * faceCount; + + auto* loadDef = static_cast(zone->GetMemory()->Alloc(offsetof(GfxImageLoadDef, data) + dataSize)); + image->texture.loadDef = loadDef; + loadDef->levelCount = static_cast(mipCount); + loadDef->flags = 0; + if (!texture->HasMipMaps()) + loadDef->flags |= iwi6::IMG_FLAG_NOMIPMAPS; + if (texture->GetTextureType() == TextureType::T_CUBE) + loadDef->flags |= iwi6::IMG_FLAG_CUBEMAP; + if (texture->GetTextureType() == TextureType::T_3D) + loadDef->flags |= iwi6::IMG_FLAG_VOLMAP; + loadDef->dimensions[0] = image->width; + loadDef->dimensions[1] = image->height; + loadDef->dimensions[2] = image->depth; + loadDef->format = static_cast(texture->GetFormat()->GetD3DFormat()); + loadDef->resourceSize = dataSize; + + char* currentDataBuffer = loadDef->data; + for (auto mipLevel = 0; mipLevel < mipCount; mipLevel++) + { + const auto mipSize = texture->GetSizeOfMipLevel(mipLevel); + + for (auto face = 0; face < faceCount; face++) + { + memcpy(currentDataBuffer, texture->GetBufferForMipLevel(mipLevel, face), mipSize); + currentDataBuffer += mipSize; + } + } + + manager->AddAsset(ASSET_TYPE_IMAGE, assetName, image); + + return true; +} diff --git a/src/ObjLoading/Game/IW3/AssetLoaders/AssetLoaderGfxImage.h b/src/ObjLoading/Game/IW3/AssetLoaders/AssetLoaderGfxImage.h new file mode 100644 index 00000000..c9541a61 --- /dev/null +++ b/src/ObjLoading/Game/IW3/AssetLoaders/AssetLoaderGfxImage.h @@ -0,0 +1,16 @@ +#pragma once +#include "Game/IW3/IW3.h" +#include "AssetLoading/BasicAssetLoader.h" +#include "AssetLoading/IAssetLoadingManager.h" +#include "SearchPath/ISearchPath.h" + +namespace IW3 +{ + class AssetLoaderGfxImage final : public BasicAssetLoader + { + public: + _NODISCARD void* CreateEmptyAsset(const std::string& assetName, MemoryManager* memory) override; + _NODISCARD bool CanLoadFromRaw() const override; + bool LoadFromRaw(const std::string& assetName, ISearchPath* searchPath, MemoryManager* memory, IAssetLoadingManager* manager, Zone* zone) const override; + }; +} diff --git a/src/ObjLoading/Game/IW3/ObjLoaderIW3.cpp b/src/ObjLoading/Game/IW3/ObjLoaderIW3.cpp index 566cc762..3f57c37c 100644 --- a/src/ObjLoading/Game/IW3/ObjLoaderIW3.cpp +++ b/src/ObjLoading/Game/IW3/ObjLoaderIW3.cpp @@ -4,6 +4,7 @@ #include "Game/IW3/GameAssetPoolIW3.h" #include "ObjContainer/IPak/IPak.h" #include "ObjLoading.h" +#include "AssetLoaders/AssetLoaderGfxImage.h" #include "AssetLoaders/AssetLoaderRawFile.h" #include "AssetLoading/AssetLoadingManager.h" #include "Image/Dx9TextureLoader.h" @@ -23,7 +24,7 @@ ObjLoader::ObjLoader() REGISTER_ASSET_LOADER(BASIC_LOADER(ASSET_TYPE_XMODEL, XModel)) REGISTER_ASSET_LOADER(BASIC_LOADER(ASSET_TYPE_MATERIAL, Material)) REGISTER_ASSET_LOADER(BASIC_LOADER(ASSET_TYPE_TECHNIQUE_SET, MaterialTechniqueSet)) - REGISTER_ASSET_LOADER(BASIC_LOADER(ASSET_TYPE_IMAGE, GfxImage)) + REGISTER_ASSET_LOADER(AssetLoaderGfxImage) REGISTER_ASSET_LOADER(BASIC_LOADER(ASSET_TYPE_SOUND, snd_alias_list_t)) REGISTER_ASSET_LOADER(BASIC_LOADER(ASSET_TYPE_SOUND_CURVE, SndCurve)) REGISTER_ASSET_LOADER(BASIC_LOADER(ASSET_TYPE_LOADED_SOUND, LoadedSound)) diff --git a/src/ObjLoading/Image/DdsLoader.cpp b/src/ObjLoading/Image/DdsLoader.cpp new file mode 100644 index 00000000..d26d566e --- /dev/null +++ b/src/ObjLoading/Image/DdsLoader.cpp @@ -0,0 +1,280 @@ +#include "DdsLoader.h" + +#include + +#include "Utils/ClassUtils.h" +#include "Utils/FileUtils.h" +#include "Image/DdsTypes.h" + +class DdsLoaderInternal +{ + static constexpr auto DDS_MAGIC = FileUtils::MakeMagic32('D', 'D', 'S', ' '); + + MemoryManager* m_memory_manager; + std::istream& m_stream; + + TextureType m_texture_type; + bool m_has_mip_maps; + size_t m_width; + size_t m_height; + size_t m_depth; + const ImageFormat* m_format; + + _NODISCARD bool ReadMagic() const + { + uint32_t magic; + m_stream.read(reinterpret_cast(&magic), sizeof(magic)); + if (m_stream.gcount() != sizeof(magic)) + { + std::cout << "Failed to read dds data" << std::endl; + return false; + } + + if (magic != DDS_MAGIC) + { + std::cout << "Invalid magic for dds" << std::endl; + return false; + } + + return true; + } + + _NODISCARD bool ReadDxt10Header() + { + DDS_HEADER_DXT10 headerDx10{}; + m_stream.read(reinterpret_cast(&headerDx10), sizeof(headerDx10)); + if (m_stream.gcount() != sizeof(headerDx10)) + { + std::cout << "Failed to read dds data" << std::endl; + return false; + } + + if (headerDx10.resourceDimension == D3D10_RESOURCE_DIMENSION_TEXTURE3D) + { + m_texture_type = TextureType::T_3D; + } + else if (headerDx10.resourceDimension == D3D10_RESOURCE_DIMENSION_TEXTURE2D) + { + if (headerDx10.miscFlag & DDS_RESOURCE_MISC_TEXTURECUBE || headerDx10.arraySize == 6) + { + m_texture_type = TextureType::T_CUBE; + } + else + { + m_texture_type = TextureType::T_2D; + } + } + else + { + std::cout << "Unsupported dds resourceDimension " << headerDx10.resourceDimension << std::endl; + return false; + } + + for (const auto* imageFormat : ImageFormat::ALL_FORMATS) + { + if (imageFormat->GetDxgiFormat() == headerDx10.dxgiFormat) + { + m_format = imageFormat; + return true; + } + } + + std::cout << "Unsupported dds dxgi format " << headerDx10.dxgiFormat << std::endl; + return false; + } + + _NODISCARD bool ReadPixelFormatFourCc(DDS_PIXELFORMAT& pf) + { + switch (pf.dwFourCC) + { + case FileUtils::MakeMagic32('D', 'X', 'T', '1'): + m_format = &ImageFormat::FORMAT_BC1; + return true; + + case FileUtils::MakeMagic32('D', 'X', 'T', '3'): + m_format = &ImageFormat::FORMAT_BC2; + return true; + + case FileUtils::MakeMagic32('D', 'X', 'T', '5'): + m_format = &ImageFormat::FORMAT_BC3; + return true; + + case FileUtils::MakeMagic32('D', 'X', '1', '0'): + return ReadDxt10Header(); + + default: + std::cout << "Unknown dds FourCC " << pf.dwFourCC << std::endl; + return false; + } + } + + static void ExtractSizeAndOffsetFromMask(uint32_t mask, unsigned& offset, unsigned& size) + { + offset = 0; + size = 0; + + if (mask == 0) + return; + + while ((mask & 1) == 0) + { + offset++; + mask >>= 1; + } + + while ((mask & 1) == 1) + { + size++; + mask >>= 1; + } + } + + _NODISCARD bool ReadPixelFormatUnsigned(DDS_PIXELFORMAT& pf) + { + unsigned rOffset, rSize, gOffset, gSize, bOffset, bSize, aOffset, aSize; + + ExtractSizeAndOffsetFromMask(pf.dwRBitMask, rOffset, rSize); + ExtractSizeAndOffsetFromMask(pf.dwGBitMask, gOffset, gSize); + ExtractSizeAndOffsetFromMask(pf.dwBBitMask, bOffset, bSize); + ExtractSizeAndOffsetFromMask(pf.dwABitMask, aOffset, aSize); + + for (const auto* imageFormat : ImageFormat::ALL_FORMATS) + { + if (imageFormat->GetType() != ImageFormatType::UNSIGNED) + continue; + + const auto* unsignedImageFormat = dynamic_cast(imageFormat); + + if (unsignedImageFormat->m_r_offset == rOffset && unsignedImageFormat->m_r_size == rSize + && unsignedImageFormat->m_g_offset == gOffset && unsignedImageFormat->m_g_size == gSize + && unsignedImageFormat->m_b_offset == bOffset && unsignedImageFormat->m_b_size == bSize + && unsignedImageFormat->m_a_offset == aOffset && unsignedImageFormat->m_a_size == aSize) + { + m_format = imageFormat; + return true; + } + } + + std::cout << "Failed to find dds pixel format: R=" << std::hex << pf.dwRBitMask + << " G=" << std::hex << pf.dwGBitMask + << " B=" << std::hex << pf.dwBBitMask + << " A=" << std::hex << pf.dwABitMask << std::endl; + + return false; + } + + _NODISCARD bool ReadPixelFormat(DDS_PIXELFORMAT& pf) + { + if (pf.dwFlags & DDPF_FOURCC) + return ReadPixelFormatFourCc(pf); + + return ReadPixelFormatUnsigned(pf); + } + + _NODISCARD bool ReadHeader() + { + DDS_HEADER header{}; + m_stream.read(reinterpret_cast(&header), sizeof(header)); + if (m_stream.gcount() != sizeof(header)) + { + std::cout << "Failed to read dds data" << std::endl; + return false; + } + + m_width = header.dwWidth; + m_height = header.dwHeight; + m_depth = header.dwDepth; + m_has_mip_maps = (header.dwCaps & DDSCAPS_MIPMAP) != 0 || header.dwMipMapCount > 1; + + if (header.dwCaps2 & DDSCAPS2_CUBEMAP) + m_texture_type = TextureType::T_CUBE; + else if (header.dwDepth > 1) + m_texture_type = TextureType::T_3D; + else + m_texture_type = TextureType::T_2D; + + return ReadPixelFormat(header.ddspf); + } + + _NODISCARD Texture* ReadTextureData() const + { + Texture* result; + + switch (m_texture_type) + { + case TextureType::T_2D: + result = new Texture2D(m_format, m_width, m_height, m_has_mip_maps); + break; + + case TextureType::T_3D: + result = new Texture3D(m_format, m_width, m_height, m_depth, m_has_mip_maps); + break; + + case TextureType::T_CUBE: + result = new TextureCube(m_format, m_width, m_height, m_has_mip_maps); + break; + + default: + return nullptr; + } + + const auto mipMapCount = m_has_mip_maps ? result->GetMipMapCount() : 1; + const auto faceCount = m_texture_type == TextureType::T_CUBE ? 6 : 1; + + result->Allocate(); + + for (auto mipLevel = 0; mipLevel < mipMapCount; mipLevel++) + { + const auto mipSize = result->GetSizeOfMipLevel(mipLevel); + + for (auto face = 0; face < faceCount; face++) + { + m_stream.read(reinterpret_cast(result->GetBufferForMipLevel(mipLevel, face)), mipSize); + + if (m_stream.gcount() != mipSize) + { + std::cout << "Failed to read texture data from dds" << std::endl; + delete result; + return nullptr; + } + } + } + + return result; + } + +public: + DdsLoaderInternal(MemoryManager* memoryManager, std::istream& stream) + : m_memory_manager(memoryManager), + m_stream(stream), + m_texture_type(TextureType::T_2D), + m_has_mip_maps(false), + m_width(0u), + m_height(0u), + m_depth(0u), + m_format(nullptr) + { + } + + Texture* LoadDds() + { + if (!ReadMagic() + || !ReadHeader()) + { + return nullptr; + } + + return ReadTextureData(); + } +}; + +DdsLoader::DdsLoader(MemoryManager* memoryManager) + : m_memory_manager(memoryManager) +{ +} + +Texture* DdsLoader::LoadDds(std::istream& stream) const +{ + DdsLoaderInternal internal(m_memory_manager, stream); + return internal.LoadDds(); +} diff --git a/src/ObjLoading/Image/DdsLoader.h b/src/ObjLoading/Image/DdsLoader.h new file mode 100644 index 00000000..6d80ab36 --- /dev/null +++ b/src/ObjLoading/Image/DdsLoader.h @@ -0,0 +1,15 @@ +#pragma once + +#include +#include "Utils/MemoryManager.h" +#include "Image/Texture.h" + +class DdsLoader +{ + MemoryManager* m_memory_manager; + +public: + explicit DdsLoader(MemoryManager* memoryManager); + + Texture* LoadDds(std::istream& stream) const; +}; diff --git a/src/ObjWriting/Image/DdsWriter.cpp b/src/ObjWriting/Image/DdsWriter.cpp index 5934ce7e..fef3429c 100644 --- a/src/ObjWriting/Image/DdsWriter.cpp +++ b/src/ObjWriting/Image/DdsWriter.cpp @@ -4,7 +4,6 @@ #include #include - #include "Image/DdsTypes.h" #include "Image/TextureConverter.h"