Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[Graphics] Streamed compressed texture could end up having their init…
…ial texture size not multiple of 4, resulting in error at runtime loading. Skip these cases for now.
  • Loading branch information
xen2 committed Oct 18, 2019
1 parent e2c2eb1 commit e8c8e4a
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 27 deletions.
Expand Up @@ -28,6 +28,11 @@ public struct ChunkEntry
public int Size;
}

/// <summary>
/// True if data is followed by initial low resolution image.
/// </summary>
public bool InitialImage;

/// <summary>
/// The data container url.
/// </summary>
Expand Down Expand Up @@ -60,6 +65,7 @@ public struct ChunkEntry
public void Write(SerializationStream stream)
{
stream.Write(1);
stream.Write(InitialImage);
stream.Write(DataUrl);
stream.Write(PackageTime.Ticks);
stream.Write(ChunksCount);
Expand All @@ -83,6 +89,7 @@ public static void Read(SerializationStream stream, out ContentStorageHeader res
var version = stream.ReadInt32();
if (version == 1)
{
result.InitialImage = stream.ReadBoolean();
result.DataUrl = stream.ReadString();
result.PackageTime = new DateTime(stream.ReadInt64());
int chunksCount = stream.ReadInt32();
Expand Down
Expand Up @@ -123,6 +123,14 @@ public SpriteSheetCommand(string url, SpriteSheetParameters parameters, IAssetFi
Version = 2;
}

protected override void ComputeAssemblyHash(BinarySerializationWriter writer)
{
base.ComputeAssemblyHash(writer);

// If texture format changes, we want to compile again
writer.Write(TextureSerializationData.Version);
}

protected override Task<ResultStatus> DoCommandOverride(ICommandContext commandContext)
{
var assetManager = new ContentManager(MicrothreadLocalDatabases.ProviderService);
Expand Down
16 changes: 11 additions & 5 deletions sources/engine/Xenko.Graphics/Data/TextureContentSerializer.cs
Expand Up @@ -87,19 +87,25 @@ internal static void Serialize(ArchiveMode mode, SerializationStream stream, Tex
}

// Load initial texture (with limited number of mipmaps)
using (var textureData = Image.Load(stream.NativeStream))
if (storageHeader.InitialImage)
{
if (texture.GraphicsDevice != null)
texture.OnDestroyed(); //Allows fast reloading todo review maybe?
using (var textureData = Image.Load(stream.NativeStream))
{
if (texture.GraphicsDevice != null)
texture.OnDestroyed(); //Allows fast reloading todo review maybe?

texture.InitializeFrom(textureData.Description, new TextureViewDescription(), textureData.ToDataBox());
texture.InitializeFrom(textureData.Description, new TextureViewDescription(), textureData.ToDataBox());
}
}
}
else
{
// Load initial texture and discard it (we are going to load the full chunk texture right after)
using (var textureData = Image.Load(stream.NativeStream))
if (storageHeader.InitialImage)
{
using (var textureData = Image.Load(stream.NativeStream))
{
}
}

// Deserialize whole texture without streaming feature
Expand Down
59 changes: 37 additions & 22 deletions sources/engine/Xenko.Graphics/Data/TextureSerializationData.cs
Expand Up @@ -14,7 +14,7 @@ namespace Xenko.Graphics.Data
/// </summary>
public sealed class TextureSerializationData
{
internal const int Version = 5;
internal const int Version = 6;

/// <summary>
/// Texture with a mip map count equal or less than this won't use streaming.
Expand Down Expand Up @@ -74,36 +74,51 @@ public void Write(SerializationStream stream)
// Write image header
ImageHelper.ImageDescriptionSerializer.Serialize(ref Image.Description, ArchiveMode.Serialize, stream);

// Count number of mip maps that won't be part of initial load (they will be available through streaming)
int skippedMipCount = Image.Description.MipLevels - InitialNonStreamedMipCount;

// Determine whether we can store initial image
StorageHeader.InitialImage = true;
if (Image.Description.Format.IsCompressed())
{
// Compressed: mips need to be multiple of 4, otherwise we can't do it
var initialImageWidth = Image.PixelBuffers[skippedMipCount].Width;
var initialImageHeight = Image.PixelBuffers[skippedMipCount].Height;
if (initialImageWidth % 4 != 0 || initialImageHeight % 4 != 0)
StorageHeader.InitialImage = false;
}

// Write storage header
Debug.Assert(!string.IsNullOrEmpty(StorageHeader.DataUrl));
StorageHeader.Write(stream);

// Note: in this scenario, we serialize only SkipStreamingMipCount (we know number is strictly higher than this due to previous check)
var newDesc = Image.Description;
newDesc.MipLevels = InitialNonStreamedMipCount;
var pixelBuffers = new PixelBuffer[Image.Description.ArraySize * InitialNonStreamedMipCount];

// Count number of mip maps that won't be part of initial load (they will be available through streaming)
int skippedMipCount = Image.Description.MipLevels - InitialNonStreamedMipCount;
for (uint item = 0; item < Image.Description.ArraySize; ++item)
if (StorageHeader.InitialImage)
{
for (uint level = 0; level < InitialNonStreamedMipCount; ++level)
// Note: in this scenario, we serialize only SkipStreamingMipCount (we know number is strictly higher than this due to previous check)
var newDesc = Image.Description;
newDesc.MipLevels = InitialNonStreamedMipCount;
var pixelBuffers = new PixelBuffer[Image.Description.ArraySize * InitialNonStreamedMipCount];

for (uint item = 0; item < Image.Description.ArraySize; ++item)
{
pixelBuffers[item * InitialNonStreamedMipCount + level] = Image.PixelBuffers[item * Image.Description.MipLevels + level + skippedMipCount];
for (uint level = 0; level < InitialNonStreamedMipCount; ++level)
{
pixelBuffers[item * InitialNonStreamedMipCount + level] = Image.PixelBuffers[item * Image.Description.MipLevels + level + skippedMipCount];
}
}
}

// Adjust new Width/Height
newDesc.Width = pixelBuffers[0].Width;
newDesc.Height = pixelBuffers[0].Height;
// Adjust new Width/Height
newDesc.Width = pixelBuffers[0].Width;
newDesc.Height = pixelBuffers[0].Height;

var initialImage = new Image
{
Description = newDesc,
PixelBuffers = pixelBuffers,
};
// TODO: We end up duplicating some of the texture data; we could find a way to avoid that by saving only the chunks of higher level mips?
initialImage.Save(stream.NativeStream, ImageFileType.Xenko);
var initialImage = new Image
{
Description = newDesc,
PixelBuffers = pixelBuffers,
};
// TODO: We end up duplicating some of the texture data; we could find a way to avoid that by saving only the chunks of higher level mips?
initialImage.Save(stream.NativeStream, ImageFileType.Xenko);
}
}
else
{
Expand Down

0 comments on commit e8c8e4a

Please sign in to comment.