1.5, added stuff, fixed stuff
- mod icon - fixes for new vanilla clipping issues - GenClosest optimization improving repairing, wardening, tending jobs - caching for new ListerBuildings methods - DynamicDrawManager single threading - Type.GetFields caching - StatParts analyzer category - removed duplicate steam warning and optionally window - added exception handling for debug menu, incidentworker, Graphic_Multi, InitializeComps, making all those now log what mod broke and not cause black screens or whatever
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using PerformanceFish.Prepatching;
|
||||
|
||||
namespace PerformanceFish.Rendering;
|
||||
|
||||
public sealed class ContentFinderCaching : ClassWithFishPrepatches
|
||||
{
|
||||
public sealed class Get_Patch : FishPrepatch
|
||||
{
|
||||
public override string? Description { get; }
|
||||
= "Caches texture lookups on the ContentFinder. This has a relatively large performance impact when used "
|
||||
+ "with mods like humanoid alien races";
|
||||
|
||||
public override MethodBase TargetMethodBase { get; } = methodof(ContentFinder<Texture2D>.Get);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix<T>(string itemPath, bool reportFailure, ref T? __result, out bool __state)
|
||||
{
|
||||
ref var cache = ref Cache.ByReference<string, CacheValue<T>>.GetOrAddReference(itemPath);
|
||||
|
||||
if (!cache.Cached)
|
||||
return __state = true;
|
||||
|
||||
__result = cache.Value;
|
||||
|
||||
return __state = reportFailure && __result is null;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix<T>(string itemPath, T? __result, bool __state)
|
||||
{
|
||||
if (!__state)
|
||||
return;
|
||||
|
||||
UpdateCache(itemPath, __result);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void UpdateCache<T>(string itemPath, T? __result)
|
||||
{
|
||||
if (!OnStartup.State.Initialized)
|
||||
return;
|
||||
|
||||
ref var cache = ref Cache.ByReference<string, CacheValue<T>>.GetExistingReference(itemPath);
|
||||
|
||||
cache.Cached = true;
|
||||
cache.Value = __result;
|
||||
}
|
||||
}
|
||||
|
||||
public record struct CacheValue<T>(bool Cached, T? Value);
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.IO;
|
||||
|
||||
namespace PerformanceFish.Rendering;
|
||||
|
||||
//DDS importing from // https://github.com/sarbian/DDSLoader/blob/master/DatabaseLoaderTexture_DDS.cs
|
||||
public static class DDSLoader
|
||||
{
|
||||
private const uint
|
||||
DDSD_MIPMAPCOUNT_BIT = 0x00020000,
|
||||
DDPF_ALPHAPIXELS = 0x00000001,
|
||||
DDPF_ALPHA = 0x00000002,
|
||||
DDPF_FOURCC = 0x00000004,
|
||||
DDPF_RGB = 0x00000040,
|
||||
// DDPF_YUV = 0x00000200,
|
||||
DDPF_LUMINANCE = 0x00020000;
|
||||
// DDPF_NORMAL = 0x80000000;
|
||||
|
||||
public static string? error;
|
||||
|
||||
// DDS Texture loader inspired by
|
||||
// http://answers.unity3d.com/questions/555984/can-you-load-dds-textures-during-runtime.html#answer-707772
|
||||
// http://msdn.microsoft.com/en-us/library/bb943992.aspx
|
||||
// http://msdn.microsoft.com/en-us/library/windows/desktop/bb205578(v=vs.85).aspx
|
||||
// mipmapBias limits the number of mipmap when > 0
|
||||
public static Texture2D? LoadDDS(FileStream fileStream, out bool hasMipMaps)
|
||||
{
|
||||
hasMipMaps = false;
|
||||
using var reader = new BinaryReader(fileStream);
|
||||
|
||||
if (!FourCcEquals(reader.ReadBytes(4), "DDS "))
|
||||
{
|
||||
error = "Invalid DDS file";
|
||||
return null;
|
||||
}
|
||||
|
||||
//this header byte should be 124 for DDS image files
|
||||
if (reader.ReadUInt32() != 124u)
|
||||
{
|
||||
error = "Invalid header size";
|
||||
return null;
|
||||
}
|
||||
|
||||
var dwFlags = reader.ReadUInt32();
|
||||
var dwHeight = (int)reader.ReadUInt32();
|
||||
var dwWidth = (int)reader.ReadUInt32();
|
||||
|
||||
reader.ReadUInt64();
|
||||
var dwMipMapCount = reader.ReadUInt32();
|
||||
|
||||
if ((dwFlags & DDSD_MIPMAPCOUNT_BIT) != 0)
|
||||
hasMipMaps = dwMipMapCount > 1u;
|
||||
|
||||
// dwReserved1
|
||||
for (var i = 5; i-- > 0;)
|
||||
reader.ReadUInt64();
|
||||
|
||||
reader.ReadUInt32();
|
||||
|
||||
// DDS_PIXELFORMAT
|
||||
reader.ReadUInt32();
|
||||
var dds_pxlf_dwFlags = reader.ReadUInt32();
|
||||
var dds_pxlf_dwFourCC = reader.ReadBytes(4);
|
||||
// var fourCC = Encoding.ASCII.GetString(dds_pxlf_dwFourCC);
|
||||
var dds_pxlf_dwRGBBitCount = reader.ReadUInt32();
|
||||
var pixelSize = dds_pxlf_dwRGBBitCount / 8;
|
||||
var dds_pxlf_dwRBitMask = reader.ReadUInt32();
|
||||
var dds_pxlf_dwGBitMask = reader.ReadUInt32();
|
||||
var dds_pxlf_dwBBitMask = reader.ReadUInt32();
|
||||
var dds_pxlf_dwABitMask = reader.ReadUInt32();
|
||||
|
||||
reader.ReadUInt64();
|
||||
reader.ReadUInt64();
|
||||
reader.ReadUInt32();
|
||||
|
||||
var rgb888 = dds_pxlf_dwRBitMask == 0x000000ff
|
||||
&& dds_pxlf_dwGBitMask == 0x0000ff00
|
||||
&& dds_pxlf_dwBBitMask == 0x00ff0000;
|
||||
|
||||
var bgr888 = dds_pxlf_dwRBitMask == 0x00ff0000
|
||||
&& dds_pxlf_dwGBitMask == 0x0000ff00
|
||||
&& dds_pxlf_dwBBitMask == 0x000000ff;
|
||||
|
||||
var rgb565 = dds_pxlf_dwRBitMask == 0x0000F800
|
||||
&& dds_pxlf_dwGBitMask == 0x000007E0
|
||||
&& dds_pxlf_dwBBitMask == 0x0000001F;
|
||||
|
||||
var argb4444 = dds_pxlf_dwABitMask == 0x0000f000
|
||||
&& dds_pxlf_dwRBitMask == 0x00000f00
|
||||
&& dds_pxlf_dwGBitMask == 0x000000f0
|
||||
&& dds_pxlf_dwBBitMask == 0x0000000f;
|
||||
|
||||
var rbga4444 = dds_pxlf_dwABitMask == 0x0000000f
|
||||
&& dds_pxlf_dwRBitMask == 0x0000f000
|
||||
&& dds_pxlf_dwGBitMask == 0x000000f0
|
||||
&& dds_pxlf_dwBBitMask == 0x00000f00;
|
||||
|
||||
var textureFormat = TextureFormat.ARGB32;
|
||||
var isCompressed = false;
|
||||
|
||||
if ((dds_pxlf_dwFlags & DDPF_FOURCC) != 0)
|
||||
{
|
||||
// Texture does not contain RGB data, check FourCC for format
|
||||
isCompressed = true;
|
||||
|
||||
if (FourCcEquals(dds_pxlf_dwFourCC, "DXT1"))
|
||||
textureFormat = TextureFormat.DXT1;
|
||||
else if (FourCcEquals(dds_pxlf_dwFourCC, "DXT5"))
|
||||
textureFormat = TextureFormat.DXT5;
|
||||
else if (FourCcEquals(dds_pxlf_dwFourCC, "DX10"))
|
||||
textureFormat = TextureFormat.BC7;
|
||||
}
|
||||
else
|
||||
{
|
||||
var alphapixel = (dds_pxlf_dwFlags & DDPF_ALPHAPIXELS) != 0;
|
||||
|
||||
switch ((dds_pxlf_dwFlags & DDPF_RGB) != 0)
|
||||
{
|
||||
case true when rgb888 || bgr888:
|
||||
// RGB or RGBA format
|
||||
textureFormat = alphapixel
|
||||
? TextureFormat.RGBA32
|
||||
: TextureFormat.RGB24;
|
||||
break;
|
||||
case true when rgb565:
|
||||
// Nvidia texconv B5G6R5_UNORM
|
||||
textureFormat = TextureFormat.RGB565;
|
||||
break;
|
||||
case true when alphapixel && argb4444:
|
||||
// Nvidia texconv B4G4R4A4_UNORM
|
||||
textureFormat = TextureFormat.ARGB4444;
|
||||
break;
|
||||
case true when alphapixel && rbga4444:
|
||||
textureFormat = TextureFormat.RGBA4444;
|
||||
break;
|
||||
case false when (dds_pxlf_dwFlags & DDPF_ALPHA) != 0 != ((dds_pxlf_dwFlags & DDPF_LUMINANCE) != 0):
|
||||
// A8 format or Luminance 8
|
||||
textureFormat = TextureFormat.Alpha8;
|
||||
break;
|
||||
default:
|
||||
error = "Only BC7, DXT1, DXT5, A8, RGB24, BGR24, RGBA32, BGBR32, RGB565, ARGB4444 and "
|
||||
+ "RGBA4444 are supported";
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
var dataBias = (long)(textureFormat != TextureFormat.BC7 ? 128 : 148);
|
||||
var dxtBytesLength = reader.BaseStream.Length - dataBias;
|
||||
reader.BaseStream.Seek(dataBias, SeekOrigin.Begin);
|
||||
var dxtBytes = reader.ReadBytes((int)dxtBytesLength);
|
||||
|
||||
// Swap red and blue.
|
||||
if (!isCompressed && bgr888)
|
||||
{
|
||||
for (uint i = 0; i < dxtBytes.Length; i += pixelSize)
|
||||
{
|
||||
var b = dxtBytes[i + 0];
|
||||
var r = dxtBytes[i + 2];
|
||||
|
||||
dxtBytes[i + 0] = r;
|
||||
dxtBytes[i + 2] = b;
|
||||
}
|
||||
}
|
||||
|
||||
// QualitySettings.masterTextureLimit = 0;
|
||||
// Work around for a >Unity< Bug.
|
||||
// if QualitySettings.masterTextureLimit != 0 (half or quarter texture rez)
|
||||
// and dwWidth and dwHeight divided by 2 (or 4 for quarter rez) are not a multiple of 4
|
||||
// and we are creating a DXT5 or DXT1 texture
|
||||
// Then you get an Unity error on the "new Texture"
|
||||
|
||||
var quality = QualitySettings.masterTextureLimit;
|
||||
|
||||
// If the bug conditions are present then switch to full quality
|
||||
if (isCompressed && quality > 0 && (dwWidth >> quality) % 4 != 0 && (dwHeight >> quality) % 4 != 0)
|
||||
QualitySettings.masterTextureLimit = 0;
|
||||
|
||||
var texture = new Texture2D(dwWidth, dwHeight, textureFormat, hasMipMaps);
|
||||
texture.LoadRawTextureData(dxtBytes);
|
||||
return texture;
|
||||
}
|
||||
|
||||
private static bool FourCcEquals(IList<byte> bytes, string s)
|
||||
=> bytes.Count >= 4 && bytes[0] == s[0] && bytes[1] == s[1] && bytes[2] == s[2] && bytes[3] == s[3];
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
#if !V1_4
|
||||
using Gilzoide.ManagedJobs;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using PerformanceFish.Prepatching;
|
||||
using Unity.Jobs;
|
||||
|
||||
namespace PerformanceFish.Rendering;
|
||||
|
||||
public sealed class DynamicDrawManagerPatches : ClassWithFishPrepatches
|
||||
{
|
||||
public sealed class DrawDynamicThingsPatch : FishPrepatch
|
||||
{
|
||||
public override string? Description { get; }
|
||||
= "Optimizes rendering by reverting most of the threading added in 1.5, making 5 of the 6 parallel loops "
|
||||
+ "in DrawDynamicThings run on the main thread again. This generally improves rendering performance by "
|
||||
+ "about 10% or so";
|
||||
|
||||
public override MethodBase TargetMethodBase { get; }
|
||||
= AccessTools.DeclaredMethod(typeof(DynamicDrawManager), nameof(DynamicDrawManager.DrawDynamicThings));
|
||||
|
||||
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
|
||||
|
||||
public static void ReplacementBody(DynamicDrawManager instance)
|
||||
{
|
||||
if (!DebugViewSettings.drawThingsDynamic || instance.map.Disposed)
|
||||
return;
|
||||
|
||||
instance.drawingNow = true;
|
||||
|
||||
try
|
||||
{
|
||||
CullAndInitializeThings(instance);
|
||||
|
||||
var hasAnyThingsToDraw = ThingsToDraw.Count > 0;
|
||||
if (hasAnyThingsToDraw)
|
||||
{
|
||||
if (!DebugViewSettings.singleThreadedDrawing)
|
||||
DoPreDrawThings();
|
||||
|
||||
DrawThingsNow();
|
||||
}
|
||||
|
||||
if (PawnShadowsToDraw.Count > 0)
|
||||
DrawShadowsNow();
|
||||
|
||||
if (SilhouetteUtility.CanHighlightAny() && hasAnyThingsToDraw)
|
||||
DrawSilhouettes(instance);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error("Exception drawing dynamic things: " + ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ThingsToDraw.Clear();
|
||||
PawnShadowsToDraw.Clear();
|
||||
instance.drawingNow = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void CullAndInitializeThings(DynamicDrawManager instance)
|
||||
{
|
||||
var map = instance.map;
|
||||
var mapSizeX = map.SizeX();
|
||||
var fogGrid = map.fogGrid.fogGrid;
|
||||
var snowGrid = map.snowGrid.depthGrid;
|
||||
var checkShadows = MatBases.SunShadow.shader.isSupported;
|
||||
var currentViewRect = Find.CameraDriver.CurrentViewRect.ClipInsideMap(map).ExpandedBy(1);
|
||||
var shadowViewRect = SectionLayer_SunShadows.GetSunShadowsViewRect(map, currentViewRect);
|
||||
instance.drawThings.UnwrapArray(out var drawThings, out var drawThingsCount);
|
||||
|
||||
for (var i = 0; i < drawThingsCount; i++)
|
||||
{
|
||||
var drawThing = drawThings.UnsafeLoad(i);
|
||||
var position = drawThing.Position;
|
||||
var drawThingDef = drawThing.def;
|
||||
var cellIndex = position.CellToIndex(mapSizeX);
|
||||
if ((!fogGrid[cellIndex] || drawThingDef.seeThroughFog)
|
||||
&& (drawThingDef.hideAtSnowDepth >= 1f
|
||||
|| drawThingDef.hideAtSnowDepth >= snowGrid[cellIndex]))
|
||||
{
|
||||
if (currentViewRect.Contains(position)
|
||||
|| currentViewRect.Overlaps(drawThing.OccupiedDrawRect()))
|
||||
{
|
||||
ThingsToDraw.Add(drawThing);
|
||||
|
||||
if (!DebugViewSettings.singleThreadedDrawing)
|
||||
drawThing.DynamicDrawPhase(DrawPhase.EnsureInitialized);
|
||||
}
|
||||
else if (drawThing is Pawn pawn && checkShadows && shadowViewRect.Contains(drawThing.Position))
|
||||
{
|
||||
PawnShadowsToDraw.Add(pawn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void DrawShadowsNow()
|
||||
{
|
||||
PawnShadowsToDraw.UnwrapArray(out var pawnShadowsToDraw, out var pawnShadowsToDrawCount);
|
||||
|
||||
for (var i = 0; i < pawnShadowsToDrawCount; i++)
|
||||
{
|
||||
var pawn = pawnShadowsToDraw.UnsafeLoad(i);
|
||||
try
|
||||
{
|
||||
pawn.DrawShadowAt(pawn.DrawPos);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Exception drawing shadow for '{pawn}': {ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void DrawThingsNow()
|
||||
{
|
||||
ThingsToDraw.UnwrapArray(out var thingsToDraw, out var thingsToDrawCount);
|
||||
|
||||
for (var i = 0; i < thingsToDrawCount; i++)
|
||||
{
|
||||
var drawThing = thingsToDraw.UnsafeLoad(i);
|
||||
try
|
||||
{
|
||||
drawThing.DynamicDrawPhase(DrawPhase.Draw);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Exception drawing '{drawThing}': {ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void DoPreDrawThings()
|
||||
{
|
||||
ThingsToDraw.UnwrapArray(out PreDrawThingsJob.thingsToDraw, out var thingsToDrawCount);
|
||||
new ManagedJobParallelFor(PreDrawThingsJob)
|
||||
.Schedule(thingsToDrawCount, UnityData.GetIdealBatchCount(thingsToDrawCount)).Complete();
|
||||
}
|
||||
|
||||
public static readonly List<Thing> ThingsToDraw = [];
|
||||
public static readonly List<Pawn> PawnShadowsToDraw = [];
|
||||
public static readonly PreDrawThings PreDrawThingsJob = new();
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void DrawSilhouettes(DynamicDrawManager instance)
|
||||
{
|
||||
ThingsToDraw.UnwrapArray(out var thingsToDraw, out var thingsToDrawCount);
|
||||
|
||||
var inverseFovScale = Find.CameraDriver.InverseFovScale;
|
||||
var altitude = AltitudeLayer.Silhouettes.AltitudeFor();
|
||||
var angleAxis = Quaternion.AngleAxis(0f, Vector3.up);
|
||||
|
||||
for (var i = 0; i < thingsToDrawCount; i++)
|
||||
{
|
||||
if (thingsToDraw.UnsafeLoad(i) is not Pawn pawn || !SilhouetteUtility.ShouldDrawSilhouette(pawn))
|
||||
continue;
|
||||
|
||||
var pawnRenderer = pawn.Drawer.renderer;
|
||||
|
||||
var drawSize = pawnRenderer.SilhouetteGraphic.drawSize;
|
||||
drawSize.x = drawSize.x < 2.5f ? drawSize.x + SilhouetteUtility.AdjustScale(drawSize.x) : drawSize.x;
|
||||
drawSize.y = drawSize.y < 2.5f ? drawSize.y + SilhouetteUtility.AdjustScale(drawSize.y) : drawSize.y;
|
||||
|
||||
var pos = pawnRenderer.SilhouettePos;
|
||||
pos.y = altitude;
|
||||
|
||||
SilhouetteUtility.DrawSilhouetteJob(pawn, Matrix4x4.TRS(pos, angleAxis,
|
||||
inverseFovScale with { x = inverseFovScale.x * drawSize.x, z = inverseFovScale.z * drawSize.y }));
|
||||
}
|
||||
}
|
||||
|
||||
public class PreDrawThings : IJobParallelFor
|
||||
{
|
||||
public Thing[] thingsToDraw = Array.Empty<Thing>();
|
||||
|
||||
public void Execute(int index) => thingsToDraw[index].DynamicDrawPhase(DrawPhase.ParallelPreDraw);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1,47 +0,0 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
/*namespace PerformanceFish.Rendering;
|
||||
public sealed class GenDrawPatches : ClassWithFishPatches
|
||||
{
|
||||
public sealed class DrawMeshNowOrLater_Patch : FishPatch
|
||||
{
|
||||
public override Delegate TargetMethodGroup { get; }
|
||||
= (Action<Mesh, Vector3, Quaternion, Material, bool>)GenDraw.DrawMeshNowOrLater;
|
||||
|
||||
public static void Replacement(Mesh mesh, Vector3 loc, Quaternion quat, Material mat, bool drawNow)
|
||||
{
|
||||
if (drawNow)
|
||||
{
|
||||
if (!mat.SetPass(0))
|
||||
Log.Error("SetPass(0) call failed on material " + mat.name + " with shader " + mat.shader.name);
|
||||
Graphics.DrawMeshNow(mesh, loc, quat);
|
||||
}
|
||||
else
|
||||
{
|
||||
Graphics.DrawMesh(mesh, loc, quat, mat, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class DrawMeshNowOrLaterWithMatrix_Patch : FishPatch
|
||||
{
|
||||
public override Delegate TargetMethodGroup { get; }
|
||||
= (Action<Mesh, Matrix4x4, Material, bool>)GenDraw.DrawMeshNowOrLater;
|
||||
|
||||
public static void Replacement(Mesh mesh, Matrix4x4 matrix, Material mat, bool drawNow)
|
||||
{
|
||||
if (drawNow)
|
||||
{
|
||||
mat.SetPass(0);
|
||||
Graphics.DrawMeshNow(mesh, matrix);
|
||||
}
|
||||
else
|
||||
{
|
||||
Graphics.DrawMesh(mesh, matrix, mat, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
@@ -3,40 +3,39 @@
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
/*namespace PerformanceFish.Rendering;
|
||||
public sealed class GraphicPatches : ClassWithFishPatches
|
||||
using PerformanceFish.Prepatching;
|
||||
|
||||
namespace PerformanceFish.Rendering;
|
||||
|
||||
public sealed class GraphicPatches : ClassWithFishPrepatches
|
||||
{
|
||||
public sealed class DrawMeshInt_Patch : FishPatch
|
||||
public sealed class MultiInitFix : FishPrepatch
|
||||
{
|
||||
public override MethodBase TargetMethodInfo { get; }
|
||||
= AccessTools.DeclaredMethod(typeof(Graphic), nameof(Graphic.DrawMeshInt));
|
||||
public override string? Description { get; }
|
||||
= "Fixes a bug in this type of Graphic that causes it to return with null material when failing to find a "
|
||||
+ "texture, instead of the usual pink square seen in all other Graphics";
|
||||
|
||||
public static void Replacement(Graphic __instance, Mesh mesh, Vector3 loc, Quaternion quat, Material mat)
|
||||
public override MethodBase TargetMethodBase { get; }
|
||||
= AccessTools.DeclaredMethod(typeof(Graphic_Multi), nameof(Graphic_Multi.Init));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix(Graphic_Multi __instance)
|
||||
{
|
||||
Graphics.DrawMesh(mesh, loc, quat, mat, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class Shadow_DrawWorker_Patch : FishPatch
|
||||
{
|
||||
public override MethodBase TargetMethodInfo { get; }
|
||||
= AccessTools.DeclaredMethod(typeof(Graphic_Shadow), nameof(Graphic_Shadow.DrawWorker));
|
||||
|
||||
public static void Replacement(Graphic_Shadow __instance, Vector3 loc, Rot4 rot, ThingDef? thingDef, Thing thing,
|
||||
float extraRotation)
|
||||
{
|
||||
if (__instance.shadowMesh is null
|
||||
|| thingDef is null
|
||||
|| __instance.shadowInfo is null
|
||||
|| (Find.CurrentMap is { } map && loc.ToIntVec3().InBounds(map) && map.roofGrid.Roofed(loc.ToIntVec3()))
|
||||
|| !DebugViewSettings.drawShadows)
|
||||
{
|
||||
if (__instance.MatSingle != null)
|
||||
return;
|
||||
}
|
||||
|
||||
var position = loc + __instance.shadowInfo.offset;
|
||||
position.y = AltitudeLayer.Shadows.AltitudeFor();
|
||||
Graphics.DrawMesh(__instance.shadowMesh, position, rot.AsQuat, MatBases.SunShadowFade, 0);
|
||||
AssignBadMats(__instance);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void AssignBadMats(Graphic_Multi instance)
|
||||
{
|
||||
var mats = instance.mats;
|
||||
for (var i = mats.Length; i-- > 0;)
|
||||
{
|
||||
if (mats[i] == null)
|
||||
mats[i] = BaseContent.BadMat;
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.IO;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using FisheryLib.Pools;
|
||||
using PerformanceFish.Prepatching;
|
||||
using RimWorld.IO;
|
||||
|
||||
namespace PerformanceFish.Rendering;
|
||||
|
||||
public sealed class TextureLoadingPatches : ClassWithFishPrepatches
|
||||
{
|
||||
public const string DDS_EXTENSION = ".dds";
|
||||
|
||||
public sealed class ModContentLoader_StaticConstructor : FishPrepatch
|
||||
{
|
||||
public override string? Description { get; }
|
||||
= "Adds .dds to the accepted extensions for texture loading. Those files load faster than png, render at "
|
||||
+ "better quality and help prevent memory issues";
|
||||
|
||||
public override MethodBase TargetMethodBase { get; }
|
||||
= AccessTools.Constructor(typeof(ModContentLoader<>), searchForStatic: true);
|
||||
|
||||
public static void Postfix<T>() where T : class
|
||||
=> ModContentLoader<T>.AcceptableExtensionsTexture
|
||||
= ModContentLoader<T>.AcceptableExtensionsTexture.Add(DDS_EXTENSION);
|
||||
}
|
||||
|
||||
public sealed class ModContentLoader_LoadItem : FishPrepatch
|
||||
{
|
||||
public override string? Description { get; }
|
||||
= "Adds .dds to the accepted extensions for texture loading. Those files load faster than png, render at "
|
||||
+ "better quality and help prevent memory issues";
|
||||
|
||||
public override MethodBase TargetMethodBase { get; } = methodof(ModContentLoader<Texture2D>.LoadItem);
|
||||
|
||||
public static bool Prefix<T>(VirtualFile file, ref LoadedContentItem<T>? __result) where T : class
|
||||
{
|
||||
if (typeof(T) != typeof(Texture2D)
|
||||
|| (GetFileInfo(file)?.Extension.Equals(DDS_EXTENSION, StringComparison.OrdinalIgnoreCase) ?? true)
|
||||
|| !File.Exists(Path.ChangeExtension(file.FullPath, DDS_EXTENSION)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
__result = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ModContentLoader_LoadTexture : FishPrepatch
|
||||
{
|
||||
public override string? Description { get; }
|
||||
= "Adds .dds to the accepted extensions for texture loading. Those files load faster than png, render at "
|
||||
+ "better quality and help prevent memory issues";
|
||||
|
||||
public override MethodBase TargetMethodBase { get; } = methodof(ModContentLoader<Texture2D>.LoadTexture);
|
||||
|
||||
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
=> ilProcessor.ReplaceBodyWith(LoadTexture);
|
||||
|
||||
public static Texture2D? LoadTexture(VirtualFile file)
|
||||
{
|
||||
if (!file.Exists)
|
||||
return null;
|
||||
|
||||
Texture2D? texture2D;
|
||||
|
||||
var fileInfo = GetFileInfo(file);
|
||||
var isDds = fileInfo?.Extension.Equals(DDS_EXTENSION, StringComparison.OrdinalIgnoreCase) ?? false;
|
||||
var hasStoredMipMaps = false;
|
||||
|
||||
if (isDds)
|
||||
{
|
||||
texture2D = DDSLoader.LoadDDS(fileInfo!.OpenRead(), out hasStoredMipMaps);
|
||||
|
||||
if (!DDSLoader.error.NullOrEmpty())
|
||||
LogDDSLoadingError(file);
|
||||
|
||||
if (texture2D is null)
|
||||
{
|
||||
isDds = false;
|
||||
if ((texture2D = LoadTextureUsingFallbackFormat(file)) is null)
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var data = file.ReadAllBytes();
|
||||
texture2D = new(2, 2, TextureFormat.Alpha8, true);
|
||||
texture2D.LoadImage(data);
|
||||
FixMipMapsIfNeeded(texture2D, data, file);
|
||||
}
|
||||
|
||||
if (!isDds && Prefs.TextureCompression)
|
||||
texture2D.Compress(true);
|
||||
|
||||
texture2D.name = Path.GetFileNameWithoutExtension(file.Name);
|
||||
texture2D.filterMode = FilterMode.Trilinear;
|
||||
texture2D.anisoLevel = 0;
|
||||
|
||||
if (texture2D.mipmapCount > 1)
|
||||
texture2D.mipMapBias = -0.7f;
|
||||
|
||||
texture2D.Apply(!hasStoredMipMaps, true);
|
||||
return texture2D;
|
||||
}
|
||||
|
||||
public static Texture2D? LoadTextureUsingFallbackFormat(VirtualFile file)
|
||||
{
|
||||
if (ReadBytesForFallbackFormat(file) is not { } data)
|
||||
{
|
||||
Log.Error($"Could not load texture at {file.FullPath}.");
|
||||
return null;
|
||||
}
|
||||
|
||||
var texture2D = new Texture2D(2, 2, TextureFormat.Alpha8, true);
|
||||
texture2D.LoadImage(data);
|
||||
FixMipMapsIfNeeded(texture2D, data, file);
|
||||
return texture2D;
|
||||
}
|
||||
|
||||
public static void FixMipMapsIfNeeded(Texture2D texture2D, byte[] data, VirtualFile file)
|
||||
{
|
||||
if (((texture2D.width & 3) == 0) & ((texture2D.height & 3) == 0))
|
||||
return;
|
||||
|
||||
if (Prefs.LogVerbose)
|
||||
{
|
||||
Log.Warning($"Texture does not support mipmapping, needs to be divisible by 4 ({
|
||||
texture2D.width}x{texture2D.height}) for '{file.Name}'");
|
||||
}
|
||||
|
||||
texture2D = new(2, 2, TextureFormat.Alpha8, mipChain: false);
|
||||
texture2D.LoadImage(data);
|
||||
}
|
||||
|
||||
private static byte[]? ReadBytesForFallbackFormat(VirtualFile file)
|
||||
{
|
||||
byte[]? data = null;
|
||||
foreach (var acceptableExtension in ModContentLoader<Texture2D>.AcceptableExtensionsTexture)
|
||||
{
|
||||
if (acceptableExtension == DDS_EXTENSION)
|
||||
continue;
|
||||
|
||||
var fallbackPath = Path.ChangeExtension(file.FullPath, acceptableExtension);
|
||||
if (!File.Exists(fallbackPath))
|
||||
continue;
|
||||
|
||||
Log.Warning($"Could not load .dds at {file.FullPath}. Loading as {acceptableExtension} instead.");
|
||||
data = File.ReadAllBytes(fallbackPath);
|
||||
break;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public static void LogDDSLoadingError(VirtualFile file)
|
||||
=> Log.Warning($"DDS error at '{file.FullPath}': {DDSLoader.error}");
|
||||
}
|
||||
|
||||
public static FileInfo? GetFileInfo(VirtualFile file)
|
||||
=> file is FilesystemFile systemFile ? systemFile.fileInfo : null;
|
||||
|
||||
public sealed class GetAllFilesForModPatch : FishPrepatch
|
||||
{
|
||||
public override string? Description { get; }
|
||||
= "Adds .dds to the accepted extensions for texture loading. Those files load faster than png, render at "
|
||||
+ "better quality and help prevent memory issues";
|
||||
|
||||
public override MethodBase TargetMethodBase { get; } = methodof(ModContentPack.GetAllFilesForMod);
|
||||
|
||||
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
{
|
||||
var index = ilProcessor.instructions.FirstIndexOf(static i
|
||||
=> i.OpCode == OpCodes.Newobj
|
||||
&& i.Operand is MethodReference method
|
||||
&& method.DeclaringType.Name == "Dictionary`2");
|
||||
|
||||
ilProcessor.instructions[index].Operand = module.ImportReference(AccessTools.Constructor(
|
||||
typeof(Dictionary<string, FileInfo>), [typeof(IEqualityComparer<string>)]));
|
||||
ilProcessor.InsertAt(index, OpCodes.Call, AccessTools.PropertyGetter(typeof(StringComparer),
|
||||
nameof(StringComparer.OrdinalIgnoreCase)));
|
||||
}
|
||||
|
||||
public static void Postfix(Dictionary<string, FileInfo> __result)
|
||||
{
|
||||
using var keysToRemove = new PooledIList<List<string>>();
|
||||
using var sb = new PooledStringBuilder();
|
||||
|
||||
foreach (var pair in __result)
|
||||
{
|
||||
if (pair.Key.EndsWith(DDS_EXTENSION, StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
|
||||
sb.Append(pair.Key);
|
||||
var extensionLength = pair.Value.Extension.Length;
|
||||
sb.Remove(pair.Key.Length - extensionLength, extensionLength);
|
||||
sb.Append(DDS_EXTENSION);
|
||||
|
||||
if (__result.ContainsKey(sb.ToString()))
|
||||
keysToRemove.List.Add(pair.Key);
|
||||
|
||||
sb.Clear();
|
||||
}
|
||||
|
||||
foreach (var key in keysToRemove.List)
|
||||
__result.Remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user