diff --git a/Flax.flaxproj b/Flax.flaxproj index a7178c5cd..e135c2741 100644 --- a/Flax.flaxproj +++ b/Flax.flaxproj @@ -3,7 +3,7 @@ "Version": { "Major": 1, "Minor": 1, - "Build": 6220 + "Build": 6221 }, "Company": "Flax", "Copyright": "Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.", diff --git a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp index 5f097f638..703b4179a 100644 --- a/Source/Editor/Cooker/Steps/CookAssetsStep.cpp +++ b/Source/Editor/Cooker/Steps/CookAssetsStep.cpp @@ -25,6 +25,7 @@ #include "Engine/Core/Config/PlatformSettings.h" #include "Engine/Core/Config/GameSettings.h" #include "Engine/Core/Config/BuildSettings.h" +#include "Engine/Streaming/StreamingSettings.h" #include "Engine/ShadersCompilation/ShadersCompilation.h" #include "Engine/Graphics/RenderTools.h" #include "Engine/Graphics/Textures/TextureData.h" @@ -44,6 +45,35 @@ Dictionary CookAssetsStep::AssetProcessors; +bool CookAssetsStep::CacheEntry::IsValid(bool withDependencies) +{ + AssetInfo assetInfo; + if (Content::GetAssetInfo(ID, assetInfo)) + { + if (TypeName == assetInfo.TypeName) + { + if (FileSystem::GetFileLastEditTime(assetInfo.Path) <= FileModified) + { + bool isValid = true; + if (withDependencies) + { + for (auto& f : FileDependencies) + { + if (FileSystem::GetFileLastEditTime(f.First) > f.Second) + { + isValid = false; + break; + } + } + } + if (isValid) + return true; + } + } + } + return false; +} + CookAssetsStep::CacheEntry& CookAssetsStep::CacheData::CreateEntry(const JsonAssetBase* asset, String& cachedFilePath) { ASSERT(asset->DataTypeName.HasChars()); @@ -68,7 +98,6 @@ CookAssetsStep::CacheEntry& CookAssetsStep::CacheData::CreateEntry(const Asset* void CookAssetsStep::CacheData::InvalidateShaders() { LOG(Info, "Invalidating cached shader assets."); - for (auto e = Entries.Begin(); e.IsNotEnd(); ++e) { auto& typeName = e->Value.TypeName; @@ -83,6 +112,23 @@ void CookAssetsStep::CacheData::InvalidateShaders() } } +void CookAssetsStep::CacheData::InvalidateTextures() +{ + LOG(Info, "Invalidating cached texture assets."); + for (auto e = Entries.Begin(); e.IsNotEnd(); ++e) + { + auto& typeName = e->Value.TypeName; + if ( + typeName == Texture::TypeName || + typeName == CubeTexture::TypeName || + typeName == SpriteAtlas::TypeName + ) + { + Entries.Remove(e); + } + } +} + void CookAssetsStep::CacheData::Load(CookingData& data) { HeaderFilePath = data.CacheDirectory / String::Format(TEXT("CookedHeader_{0}.bin"), FLAXENGINE_VERSION_BUILD); @@ -159,17 +205,17 @@ void CookAssetsStep::CacheData::Load(CookingData& data) Entries.Clear(); } + const auto buildSettings = BuildSettings::Get(); + const auto gameSettings = GameSettings::Get(); + // Invalidate shaders and assets with shaders if need to rebuild them bool invalidateShaders = false; - const auto buildSettings = BuildSettings::Get(); - const bool shadersNoOptimize = buildSettings->ShadersNoOptimize; - const bool shadersGenerateDebugData = buildSettings->ShadersGenerateDebugData; - if (shadersNoOptimize != Settings.Global.ShadersNoOptimize) + if (buildSettings->ShadersNoOptimize != Settings.Global.ShadersNoOptimize) { LOG(Info, "ShadersNoOptimize option has been modified."); invalidateShaders = true; } - if (shadersGenerateDebugData != Settings.Global.ShadersGenerateDebugData) + if (buildSettings->ShadersGenerateDebugData != Settings.Global.ShadersGenerateDebugData) { LOG(Info, "ShadersGenerateDebugData option has been modified."); invalidateShaders = true; @@ -218,6 +264,12 @@ void CookAssetsStep::CacheData::Load(CookingData& data) #endif if (invalidateShaders) InvalidateShaders(); + + // Invalidate textures if streaming settings gets modified + if (Settings.Global.StreamingSettingsAssetId != gameSettings->Streaming || (Entries.ContainsKey(gameSettings->Streaming) && !Entries[gameSettings->Streaming].IsValid())) + { + InvalidateTextures(); + } } void CookAssetsStep::CacheData::Save() @@ -541,91 +593,127 @@ bool ProcessParticleEmitter(CookAssetsStep::AssetCookData& data) bool ProcessTextureBase(CookAssetsStep::AssetCookData& data) { const auto asset = static_cast(data.Asset); - - // Check if target platform doesn't support the texture format + const auto& assetHeader = asset->StreamingTexture()->GetHeader(); const auto format = asset->Format(); const auto targetFormat = data.Data.Tools->GetTextureFormat(data.Data, asset, format); + const auto streamingSettings = StreamingSettings::Get(); + int32 mipLevelsMax = GPU_MAX_TEXTURE_MIP_LEVELS; + if (assetHeader->TextureGroup >= 0 && assetHeader->TextureGroup < streamingSettings->TextureGroups.Count()) + { + auto& group = streamingSettings->TextureGroups[assetHeader->TextureGroup]; + mipLevelsMax = group.MipLevelsMax; + group.MipLevelsMaxPerPlatform.TryGet(data.Data.Tools->GetPlatform(), mipLevelsMax); + } + + // Faster path if don't need to modify texture for the target platform + if (format == targetFormat && assetHeader->MipLevels <= mipLevelsMax) + { + return CookAssetsStep::ProcessDefaultAsset(data); + } + + // Extract texture data from the asset + TextureData textureDataSrc; + auto assetLock = asset->LockData(); + if (asset->GetTextureData(textureDataSrc, false)) + { + LOG(Error, "Failed to load data from texture {0}", asset->ToString()); + return true; + } + + TextureData* textureData = &textureDataSrc; + TextureData textureDataTmp1; + if (format != targetFormat) { - // Extract texture data from the asset - TextureData textureData; - auto assetLock = asset->LockData(); - if (asset->GetTextureData(textureData, false)) - { - LOG(Error, "Failed to load data from texture {0}", asset->ToString()); - return true; - } - // Convert texture data to the target format - TextureData targetTextureData; - if (TextureTool::Convert(targetTextureData, textureData, targetFormat)) + if (TextureTool::Convert(textureDataTmp1, *textureData, targetFormat)) { LOG(Error, "Failed to convert texture {0} from format {1} to {2}", asset->ToString(), (int32)format, (int32)targetFormat); return true; } - - // Adjust texture header - auto& header = *(TextureHeader*)data.InitData.CustomData.Get(); - header.Width = targetTextureData.Width; - header.Height = targetTextureData.Height; - header.Format = targetTextureData.Format; - header.MipLevels = targetTextureData.GetMipLevels(); - - // Serialize texture data into the asset chunks - for (int32 mipIndex = 0; mipIndex < targetTextureData.GetMipLevels(); mipIndex++) - { - auto chunk = New(); - data.InitData.Header.Chunks[mipIndex] = chunk; - - // Calculate the texture data storage layout - uint32 rowPitch, slicePitch; - const int32 mipWidth = Math::Max(1, targetTextureData.Width >> mipIndex); - const int32 mipHeight = Math::Max(1, targetTextureData.Height >> mipIndex); - RenderTools::ComputePitch(targetTextureData.Format, mipWidth, mipHeight, rowPitch, slicePitch); - chunk->Data.Allocate(slicePitch * targetTextureData.GetArraySize()); - - // Copy array slices into mip data (sequential) - for (int32 arrayIndex = 0; arrayIndex < targetTextureData.Items.Count(); arrayIndex++) - { - auto& mipData = targetTextureData.Items[arrayIndex].Mips[mipIndex]; - byte* src = mipData.Data.Get(); - byte* dst = chunk->Data.Get() + (slicePitch * arrayIndex); - - // Faster path if source and destination data layout matches - if (rowPitch == mipData.RowPitch && slicePitch == mipData.DepthPitch) - { - Platform::MemoryCopy(dst, src, slicePitch); - } - else - { - const auto copyRowSize = Math::Min(mipData.RowPitch, rowPitch); - for (uint32 line = 0; line < mipData.Lines; line++) - { - Platform::MemoryCopy(dst, src, copyRowSize); - src += mipData.RowPitch; - dst += rowPitch; - } - } - } - } - - // Clone any custom asset chunks (eg. sprite atlas data, mips are in 0-13 chunks) - for (int32 i = 14; i < ASSET_FILE_DATA_CHUNKS; i++) - { - const auto chunk = asset->GetChunk(i); - if (chunk != nullptr && chunk->IsMissing() && chunk->ExistsInFile()) - { - if (asset->Storage->LoadAssetChunk(chunk)) - return true; - data.InitData.Header.Chunks[i] = chunk->Clone(); - } - } - - return false; + textureData = &textureDataSrc; } - // Fallback to the default asset processing - return CookAssetsStep::ProcessDefaultAsset(data); + if (assetHeader->MipLevels > mipLevelsMax) + { + // Reduce texture quality + const int32 mipLevelsToStrip = assetHeader->MipLevels - mipLevelsMax; + textureData->Width = Math::Max(1, textureData->Width >> mipLevelsToStrip); + textureData->Height = Math::Max(1, textureData->Height >> mipLevelsToStrip); + textureData->Depth = Math::Max(1, textureData->Depth >> mipLevelsToStrip); + for (int32 arrayIndex = 0; arrayIndex < textureData->Items.Count(); arrayIndex++) + { + auto& item = textureData->Items[arrayIndex]; + Array> oldMips(MoveTemp(item.Mips)); + item.Mips.Resize(mipLevelsMax); + for (int32 mipIndex = 0; mipIndex < mipLevelsMax; mipIndex++) + { + auto& dstMip = item.Mips[mipIndex]; + auto& srcMip = oldMips[mipIndex + mipLevelsToStrip]; + dstMip = MoveTemp(srcMip); + } + } + } + + // Adjust texture header + auto& header = *(TextureHeader*)data.InitData.CustomData.Get(); + header.Width = textureData->Width; + header.Height = textureData->Height; + header.Depth = textureData->Depth; + header.Format = textureData->Format; + header.MipLevels = textureData->GetMipLevels(); + + // Serialize texture data into the asset chunks + for (int32 mipIndex = 0; mipIndex < textureData->GetMipLevels(); mipIndex++) + { + auto chunk = New(); + data.InitData.Header.Chunks[mipIndex] = chunk; + + // Calculate the texture data storage layout + uint32 rowPitch, slicePitch; + const int32 mipWidth = Math::Max(1, textureData->Width >> mipIndex); + const int32 mipHeight = Math::Max(1, textureData->Height >> mipIndex); + RenderTools::ComputePitch(textureData->Format, mipWidth, mipHeight, rowPitch, slicePitch); + chunk->Data.Allocate(slicePitch * textureData->GetArraySize()); + + // Copy array slices into mip data (sequential) + for (int32 arrayIndex = 0; arrayIndex < textureData->Items.Count(); arrayIndex++) + { + auto& mipData = textureData->Items[arrayIndex].Mips[mipIndex]; + byte* src = mipData.Data.Get(); + byte* dst = chunk->Data.Get() + (slicePitch * arrayIndex); + + // Faster path if source and destination data layout matches + if (rowPitch == mipData.RowPitch && slicePitch == mipData.DepthPitch) + { + Platform::MemoryCopy(dst, src, slicePitch); + } + else + { + const auto copyRowSize = Math::Min(mipData.RowPitch, rowPitch); + for (uint32 line = 0; line < mipData.Lines; line++) + { + Platform::MemoryCopy(dst, src, copyRowSize); + src += mipData.RowPitch; + dst += rowPitch; + } + } + } + } + + // Clone any custom asset chunks (eg. sprite atlas data, mips are in 0-13 chunks) + for (int32 i = 14; i < ASSET_FILE_DATA_CHUNKS; i++) + { + const auto chunk = asset->GetChunk(i); + if (chunk != nullptr && chunk->IsMissing() && chunk->ExistsInFile()) + { + if (asset->Storage->LoadAssetChunk(chunk)) + return true; + data.InitData.Header.Chunks[i] = chunk->Clone(); + } + } + + return false; } CookAssetsStep::CookAssetsStep() @@ -938,6 +1026,7 @@ bool CookAssetsStep::Perform(CookingData& data) { cache.Settings.Global.ShadersNoOptimize = buildSettings->ShadersNoOptimize; cache.Settings.Global.ShadersGenerateDebugData = buildSettings->ShadersGenerateDebugData; + cache.Settings.Global.StreamingSettingsAssetId = gameSettings->Streaming; } // Note: this step converts all the assets (even the json) into the binary files (FlaxStorage format). diff --git a/Source/Editor/Cooker/Steps/CookAssetsStep.h b/Source/Editor/Cooker/Steps/CookAssetsStep.h index 8896e1e99..f134d3dd9 100644 --- a/Source/Editor/Cooker/Steps/CookAssetsStep.h +++ b/Source/Editor/Cooker/Steps/CookAssetsStep.h @@ -49,6 +49,8 @@ public: /// The list of files on which this entry depends on. Cached date is the last edit time used to discard cache result on modification. /// FileDependenciesList FileDependencies; + + bool IsValid(bool withDependencies = false); }; /// @@ -93,6 +95,7 @@ public: { bool ShadersNoOptimize; bool ShadersGenerateDebugData; + Guid StreamingSettingsAssetId; } Global; } Settings; @@ -134,6 +137,11 @@ public: /// void InvalidateShaders(); + /// + /// Removes all cached entries for assets that contain a texture. This forces rebuild for them. + /// + void InvalidateTextures(); + /// /// Loads the cache for the given cooking data. /// diff --git a/Source/Engine/Graphics/Textures/TextureBase.cpp b/Source/Engine/Graphics/Textures/TextureBase.cpp index a4aeccbcb..59acc488e 100644 --- a/Source/Engine/Graphics/Textures/TextureBase.cpp +++ b/Source/Engine/Graphics/Textures/TextureBase.cpp @@ -12,6 +12,51 @@ #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Content/Factories/BinaryAssetFactory.h" +TextureMipData::TextureMipData() + : RowPitch(0) + , DepthPitch(0) + , Lines(0) +{ +} + +TextureMipData::TextureMipData(const TextureMipData& other) + : RowPitch(other.RowPitch) + , DepthPitch(other.DepthPitch) + , Lines(other.Lines) + , Data(other.Data) +{ +} + +TextureMipData::TextureMipData(TextureMipData&& other) noexcept + : RowPitch(other.RowPitch) + , DepthPitch(other.DepthPitch) + , Lines(other.Lines) + , Data(std::move(other.Data)) +{ +} + +TextureMipData& TextureMipData::operator=(const TextureMipData& other) +{ + if (this == &other) + return *this; + RowPitch = other.RowPitch; + DepthPitch = other.DepthPitch; + Lines = other.Lines; + Data = other.Data; + return *this; +} + +TextureMipData& TextureMipData::operator=(TextureMipData&& other) noexcept +{ + if (this == &other) + return *this; + RowPitch = other.RowPitch; + DepthPitch = other.DepthPitch; + Lines = other.Lines; + Data = MoveTemp(other.Data); + return *this; +} + REGISTER_BINARY_ASSET_ABSTRACT(TextureBase, "FlaxEngine.TextureBase"); TextureBase::TextureBase(const SpawnParams& params, const AssetInfo* info) diff --git a/Source/Engine/Graphics/Textures/TextureData.h b/Source/Engine/Graphics/Textures/TextureData.h index e5e1eba57..dcae204c8 100644 --- a/Source/Engine/Graphics/Textures/TextureData.h +++ b/Source/Engine/Graphics/Textures/TextureData.h @@ -19,6 +19,12 @@ public: uint32 Lines; BytesContainer Data; + TextureMipData(); + TextureMipData(const TextureMipData& other); + TextureMipData(TextureMipData&& other) noexcept; + TextureMipData& operator=(const TextureMipData& other); + TextureMipData& operator=(TextureMipData&& other) noexcept; + template T& Get(int32 x, int32 y) { @@ -120,7 +126,6 @@ public: /// /// Gets amount of textures in the array /// - /// Array size int32 GetArraySize() const { return Items.Count(); @@ -129,7 +134,6 @@ public: /// /// Gets amount of mip maps in the textures /// - /// Amount of mip levels int32 GetMipLevels() const { return Items.HasItems() ? Items[0].Mips.Count() : 0; diff --git a/Source/FlaxEngine.Gen.cs b/Source/FlaxEngine.Gen.cs index 5206c98f4..2fe4bf65a 100644 --- a/Source/FlaxEngine.Gen.cs +++ b/Source/FlaxEngine.Gen.cs @@ -13,5 +13,5 @@ using System.Runtime.InteropServices; [assembly: AssemblyCulture("")] [assembly: ComVisible(false)] [assembly: Guid("b8442186-4a70-7c85-704a-857c262d00f6")] -[assembly: AssemblyVersion("1.1.6220")] -[assembly: AssemblyFileVersion("1.1.6220")] +[assembly: AssemblyVersion("1.1.6221")] +[assembly: AssemblyFileVersion("1.1.6221")] diff --git a/Source/FlaxEngine.Gen.h b/Source/FlaxEngine.Gen.h index 0e083b5ab..add6b8f11 100644 --- a/Source/FlaxEngine.Gen.h +++ b/Source/FlaxEngine.Gen.h @@ -3,11 +3,11 @@ #pragma once #define FLAXENGINE_NAME "FlaxEngine" -#define FLAXENGINE_VERSION Version(1, 1, 6220) -#define FLAXENGINE_VERSION_TEXT "1.1.6220" +#define FLAXENGINE_VERSION Version(1, 1, 6221) +#define FLAXENGINE_VERSION_TEXT "1.1.6221" #define FLAXENGINE_VERSION_MAJOR 1 #define FLAXENGINE_VERSION_MINOR 1 -#define FLAXENGINE_VERSION_BUILD 6220 +#define FLAXENGINE_VERSION_BUILD 6221 #define FLAXENGINE_COMPANY "Flax" #define FLAXENGINE_COPYRIGHT "Copyright (c) 2012-2021 Wojciech Figat. All rights reserved."