diff --git a/CodeWalker.Core/GameFiles/Resources/Texture.cs b/CodeWalker.Core/GameFiles/Resources/Texture.cs index 36c558ee9..c92c00857 100644 --- a/CodeWalker.Core/GameFiles/Resources/Texture.cs +++ b/CodeWalker.Core/GameFiles/Resources/Texture.cs @@ -699,16 +699,27 @@ public int CalcDataSize() var dxgifmt = DDSIO.GetDXGIFormat(Format); int div = 1; int len = 0; + bool compressed = DDSIO.DXTex.IsCompressed(dxgifmt); + int minimumLengthPerMip = compressed ? (IsBC1Based(dxgifmt) ? 8 : 16) : DDSIO.DXTex.BitsPerPixel(dxgifmt) / 8; for (int i = 0; i < Levels; i++) { - var width = Width / div; - var height = Height / div; + // Width or Height may reach 1 before the last mip level, half of 1 would be 0.5 truncated to 0. + // A texture can't have a dimension of 0, so we need to floor at 1. + var width = Math.Max(1, Width / div); + var height = Math.Max(1, Height / div); DDSIO.DXTex.ComputePitch(dxgifmt, width, height, out var rowPitch, out var slicePitch, 0); - len += slicePitch; + len += Math.Max(minimumLengthPerMip, slicePitch); div *= 2; } + return len * Depth; } + + private bool IsBC1Based(DDSIO.DXGI_FORMAT format) + { + return format == DDSIO.DXGI_FORMAT.DXGI_FORMAT_BC1_TYPELESS || format == DDSIO.DXGI_FORMAT.DXGI_FORMAT_BC1_UNORM || format == DDSIO.DXGI_FORMAT.DXGI_FORMAT_BC1_UNORM_SRGB; + } + public ushort CalculateStride() { if (Format == 0) return 0; @@ -937,9 +948,15 @@ public override void Read(ResourceDataReader reader, params object[] parameters) this.Unknown_84h = reader.ReadUInt32(); this.Unknown_88h = reader.ReadUInt32(); this.Unknown_8Ch = reader.ReadUInt32(); - + + // Ignore stride loaded from file as it may be incorrect, especially if texture is ATI2 and the file was + // previously saved in OpenIV, DDS documentation recommends recalculating this anyway + DDSIO.DXTex.ComputePitch(DDSIO.GetDXGIFormat(Format), this.Width, this.Height, out int rowPitch, out int slicePitch, 0); + this.Stride = (ushort)rowPitch; + // read reference data - this.Data = reader.ReadBlockAt(this.DataPointer, this.Format, this.Width, this.Height, this.Levels, this.Stride); + this.Data = reader.ReadBlockAt(this.DataPointer, CalcDataSize()); + //this.Format, this.Width, this.Height, this.Levels, this.Stride); } } @@ -1086,30 +1103,7 @@ public override long BlockLength /// public override void Read(ResourceDataReader reader, params object[] parameters) { - if (reader.IsGen9) - { - int fullLength = Convert.ToInt32(parameters[0]); - FullData = reader.ReadBytes(fullLength); - } - else - { - uint format = Convert.ToUInt32(parameters[0]); - int Width = Convert.ToInt32(parameters[1]); - int Height = Convert.ToInt32(parameters[2]); - int Levels = Convert.ToInt32(parameters[3]); - int Stride = Convert.ToInt32(parameters[4]); - - int fullLength = 0; - int length = Stride * Height; - for (int i = 0; i < Levels; i++) - { - fullLength += length; - length /= 4; - } - - FullData = reader.ReadBytes(fullLength); - } - + FullData = reader.ReadBytes((int)parameters[0]); } /// diff --git a/CodeWalker.Core/GameFiles/Utils/DDSIO.cs b/CodeWalker.Core/GameFiles/Utils/DDSIO.cs index 285487e9d..1863ce711 100644 --- a/CodeWalker.Core/GameFiles/Utils/DDSIO.cs +++ b/CodeWalker.Core/GameFiles/Utils/DDSIO.cs @@ -544,8 +544,10 @@ private static Image[] GetMipmapImages(ImageStruct img, DXGI_FORMAT format) int add = 0; for (int i = 0; i < img.MipMapLevels; i++) { - images[i].width = img.Width / div; - images[i].height = img.Height / div; + // Prevent width or height from becoming zero. One divided by anything will be < 1 which when cast to an + // int will be truncated to 0. + images[i].width = Math.Max(1, img.Width / div); + images[i].height = Math.Max(1, img.Height / div); images[i].format = format; //(DXGI_FORMAT)img.Format; images[i].pixels = buf + add; @@ -2701,32 +2703,33 @@ private static void DecompressBC5Block(BinaryReader imageReader, int x, int y, i r = (byte)(((6 - rIndex) * r0 + (rIndex - 1) * r1) / 5); } - + // Fixed copy and paste error? Most of these 'g's below were 'r's, which caused BC5 (ATI2) textures + // to not have correct colors. byte g = 255; uint gIndex = (uint)((gMask >> 3 * (4 * blockY + blockX)) & 0x07); if (gIndex == 0) { - r = r0; + g = g0; } else if (gIndex == 1) { - r = r1; + g = g1; } - else if (r0 > r1) + else if (g0 > g1) { - r = (byte)(((8 - gIndex) * r0 + (gIndex - 1) * r1) / 7); + g = (byte)(((8 - gIndex) * g0 + (gIndex - 1) * g1) / 7); } else if (gIndex == 6) { - r = 0; + g = 0; } else if (gIndex == 7) { - r = 0xff; + g = 0xff; } else { - r = (byte)(((6 - gIndex) * r0 + (gIndex - 1) * r1) / 5); + g = (byte)(((6 - gIndex) * g0 + (gIndex - 1) * g1) / 5); } diff --git a/CodeWalker/Forms/YtdForm.cs b/CodeWalker/Forms/YtdForm.cs index c966d7ec6..b8775d335 100644 --- a/CodeWalker/Forms/YtdForm.cs +++ b/CodeWalker/Forms/YtdForm.cs @@ -153,8 +153,10 @@ private void ShowTextureMip(Texture tex, int mip, bool mipchange) { int cmip = Math.Min(Math.Max(mip, 0), tex.Levels - 1); byte[] pixels = DDSIO.GetPixels(tex, cmip); - int w = tex.Width >> cmip; - int h = tex.Height >> cmip; + // Certain mipmap dimension reduction chains may lead this to become zero, so it should be clamped to be + // one at minimum. + int w = Math.Max(1, tex.Width >> cmip); + int h = Math.Max(1, tex.Height >> cmip); Bitmap bmp = new Bitmap(w, h, PixelFormat.Format32bppArgb); if (pixels != null)