Compare commits
34 Commits
clr-fix-at
...
2.0.2.83-D
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84a3293f6b | ||
|
|
28db3d6fd2 | ||
| d83ca98008 | |||
| e6df37bcca | |||
| 60d144b881 | |||
| 5089dbd6c8 | |||
|
|
abc324bf4f | ||
|
|
eee0e072bd | ||
|
|
d8335eb04f | ||
|
|
994335c6b0 | ||
|
|
172288c755 | ||
|
|
1c17be53d0 | ||
| 68b4863f52 | |||
|
|
e8f598e695 | ||
|
|
861a337029 | ||
| 06f89955d3 | |||
|
|
c7a2b679f2 | ||
|
|
bec69074a5 | ||
|
|
ac711d9a43 | ||
|
|
b875e0c3a1 | ||
|
|
46e76bbfe6 | ||
|
|
3654365f2a | ||
|
|
9b256dd185 | ||
|
|
223ade39cb | ||
|
|
5aca9e70b2 | ||
|
|
92772cf334 | ||
|
|
0395e81a9f | ||
|
|
7734a7bf7e | ||
|
|
db2d19bb1e | ||
|
|
ab305a249c | ||
|
|
9d104a9dd8 | ||
|
|
bcd3bd5ca2 | ||
|
|
c1829a9837 | ||
|
|
cca23f6e05 |
@@ -92,7 +92,7 @@ public sealed class PenumbraTexture : PenumbraBase
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
logger.LogInformation("Converting texture {Input} -> {Output} ({Target})", job.InputFile, job.OutputFile, job.TargetType);
|
||||
logger.LogDebug("Converting texture {Input} -> {Output} ({Target})", job.InputFile, job.OutputFile, job.TargetType);
|
||||
var convertTask = _convertTextureFile.Invoke(job.InputFile, job.OutputFile, job.TargetType, job.IncludeMipMaps);
|
||||
await convertTask.ConfigureAwait(false);
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<Authors></Authors>
|
||||
<Company></Company>
|
||||
<Version>2.0.3</Version>
|
||||
<Version>2.0.2.83</Version>
|
||||
<Description></Description>
|
||||
<Copyright></Copyright>
|
||||
<PackageProjectUrl>https://github.com/Light-Public-Syncshells/LightlessClient</PackageProjectUrl>
|
||||
|
||||
@@ -182,14 +182,12 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
|
||||
|
||||
if (nextAddr != IntPtr.Zero && !PtrGuard.LooksLikePtr(nextAddr))
|
||||
{
|
||||
Logger.LogWarning("[{this}] _getAddress returned non-pointer: 0x{addr:X}", this, (ulong)nextAddr);
|
||||
nextAddr = IntPtr.Zero;
|
||||
}
|
||||
|
||||
if (nextAddr != IntPtr.Zero &&
|
||||
!PtrGuard.IsReadable(nextAddr, (nuint)sizeof(FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject)))
|
||||
{
|
||||
Logger.LogWarning("[{this}] Address not readable: 0x{addr:X}", this, (ulong)nextAddr);
|
||||
nextAddr = IntPtr.Zero;
|
||||
}
|
||||
|
||||
|
||||
@@ -125,6 +125,8 @@ public sealed class Plugin : IDalamudPlugin
|
||||
services.AddSingleton<FileTransferOrchestrator>();
|
||||
services.AddSingleton<LightlessPlugin>();
|
||||
services.AddSingleton<LightlessProfileManager>();
|
||||
services.AddSingleton<TextureProcessingQueue>();
|
||||
services.AddSingleton<ModelProcessingQueue>();
|
||||
services.AddSingleton<TextureCompressionService>();
|
||||
services.AddSingleton<TextureDownscaleService>();
|
||||
services.AddSingleton<ModelDecimationService>();
|
||||
|
||||
93
LightlessSync/Services/AssetProcessingQueue.cs
Normal file
93
LightlessSync/Services/AssetProcessingQueue.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace LightlessSync.Services;
|
||||
|
||||
public sealed class AssetProcessingQueue : IDisposable
|
||||
{
|
||||
private readonly BlockingCollection<WorkItem> _queue = new();
|
||||
private readonly Thread _worker;
|
||||
private readonly ILogger _logger;
|
||||
private bool _disposed;
|
||||
|
||||
public AssetProcessingQueue(ILogger logger, string name)
|
||||
{
|
||||
_logger = logger;
|
||||
_worker = new Thread(Run)
|
||||
{
|
||||
IsBackground = true,
|
||||
Name = string.IsNullOrWhiteSpace(name) ? "LightlessSync.AssetProcessing" : name
|
||||
};
|
||||
_worker.Start();
|
||||
}
|
||||
|
||||
public Task Enqueue(Func<CancellationToken, Task> work, CancellationToken token = default)
|
||||
{
|
||||
if (work is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(work));
|
||||
}
|
||||
|
||||
var completion = new TaskCompletionSource<object?>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
completion.TrySetCanceled(token);
|
||||
return completion.Task;
|
||||
}
|
||||
|
||||
if (_queue.IsAddingCompleted || _disposed)
|
||||
{
|
||||
completion.TrySetException(new ObjectDisposedException(nameof(AssetProcessingQueue)));
|
||||
return completion.Task;
|
||||
}
|
||||
|
||||
_queue.Add(new WorkItem(work, token, completion));
|
||||
return completion.Task;
|
||||
}
|
||||
|
||||
private void Run()
|
||||
{
|
||||
foreach (var item in _queue.GetConsumingEnumerable())
|
||||
{
|
||||
if (item.Token.IsCancellationRequested)
|
||||
{
|
||||
item.Completion.TrySetCanceled(item.Token);
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
item.Work(item.Token).GetAwaiter().GetResult();
|
||||
item.Completion.TrySetResult(null);
|
||||
}
|
||||
catch (OperationCanceledException ex)
|
||||
{
|
||||
var token = ex.CancellationToken.IsCancellationRequested ? ex.CancellationToken : item.Token;
|
||||
item.Completion.TrySetCanceled(token);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Asset processing job failed.");
|
||||
item.Completion.TrySetException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
_queue.CompleteAdding();
|
||||
_worker.Join(TimeSpan.FromSeconds(2));
|
||||
_queue.Dispose();
|
||||
}
|
||||
|
||||
private readonly record struct WorkItem(
|
||||
Func<CancellationToken, Task> Work,
|
||||
CancellationToken Token,
|
||||
TaskCompletionSource<object?> Completion);
|
||||
}
|
||||
@@ -2104,6 +2104,16 @@ internal static class MdlDecimator
|
||||
}
|
||||
}
|
||||
|
||||
if (boneWeights != null
|
||||
&& blendWeightEncoding == BlendWeightEncoding.Default
|
||||
&& format.BlendWeightsElement is { } blendWeightsElement
|
||||
&& (MdlFile.VertexType)blendWeightsElement.Type == MdlFile.VertexType.UShort4
|
||||
&& ShouldTreatWeightsAsByteNormalized(boneWeights))
|
||||
{
|
||||
RescaleUShortAsByteWeights(boneWeights);
|
||||
blendWeightEncoding = BlendWeightEncoding.UShortAsByte;
|
||||
}
|
||||
|
||||
decoded = new DecodedMeshData(positions, normals, tangents, tangents2, colors, boneWeights, uvChannels, positionWs, normalWs, blendWeightEncoding);
|
||||
return true;
|
||||
}
|
||||
@@ -3413,6 +3423,44 @@ internal static class MdlDecimator
|
||||
return ToUShortNormalized(normalized);
|
||||
}
|
||||
|
||||
private static bool ShouldTreatWeightsAsByteNormalized(BoneWeight[] weights)
|
||||
{
|
||||
const float maxByteUnorm = byte.MaxValue / (float)ushort.MaxValue;
|
||||
var maxWeight = 0f;
|
||||
for (var i = 0; i < weights.Length; i++)
|
||||
{
|
||||
var weight = weights[i];
|
||||
maxWeight = Math.Max(maxWeight, weight.weight0);
|
||||
maxWeight = Math.Max(maxWeight, weight.weight1);
|
||||
maxWeight = Math.Max(maxWeight, weight.weight2);
|
||||
maxWeight = Math.Max(maxWeight, weight.weight3);
|
||||
if (maxWeight > maxByteUnorm)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return maxWeight > 0f;
|
||||
}
|
||||
|
||||
private static void RescaleUShortAsByteWeights(BoneWeight[] weights)
|
||||
{
|
||||
var scale = ushort.MaxValue / (float)byte.MaxValue;
|
||||
for (var i = 0; i < weights.Length; i++)
|
||||
{
|
||||
var weight = weights[i];
|
||||
weights[i] = new BoneWeight(
|
||||
weight.index0,
|
||||
weight.index1,
|
||||
weight.index2,
|
||||
weight.index3,
|
||||
weight.weight0 * scale,
|
||||
weight.weight1 * scale,
|
||||
weight.weight2 * scale,
|
||||
weight.weight3 * scale);
|
||||
}
|
||||
}
|
||||
|
||||
private static void NormalizeWeights(float[] weights)
|
||||
{
|
||||
var sum = weights.Sum();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using LightlessSync.FileCache;
|
||||
using LightlessSync.LightlessConfiguration;
|
||||
using LightlessSync.LightlessConfiguration.Configurations;
|
||||
using LightlessSync.Services;
|
||||
using LightlessSync.Utils;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Collections.Concurrent;
|
||||
@@ -19,6 +20,7 @@ public sealed class ModelDecimationService
|
||||
private readonly FileCacheManager _fileCacheManager;
|
||||
private readonly PlayerPerformanceConfigService _performanceConfigService;
|
||||
private readonly XivDataStorageService _xivDataStorageService;
|
||||
private readonly ModelProcessingQueue _processingQueue;
|
||||
private readonly SemaphoreSlim _decimationSemaphore = new(MaxConcurrentJobs);
|
||||
|
||||
private readonly TaskRegistry<string> _decimationDeduplicator = new();
|
||||
@@ -30,13 +32,15 @@ public sealed class ModelDecimationService
|
||||
LightlessConfigService configService,
|
||||
FileCacheManager fileCacheManager,
|
||||
PlayerPerformanceConfigService performanceConfigService,
|
||||
XivDataStorageService xivDataStorageService)
|
||||
XivDataStorageService xivDataStorageService,
|
||||
ModelProcessingQueue processingQueue)
|
||||
{
|
||||
_logger = logger;
|
||||
_configService = configService;
|
||||
_fileCacheManager = fileCacheManager;
|
||||
_performanceConfigService = performanceConfigService;
|
||||
_xivDataStorageService = xivDataStorageService;
|
||||
_processingQueue = processingQueue;
|
||||
}
|
||||
|
||||
public void ScheduleDecimation(string hash, string filePath, string? gamePath = null)
|
||||
@@ -53,9 +57,9 @@ public sealed class ModelDecimationService
|
||||
|
||||
_logger.LogDebug("Queued model decimation for {Hash}", hash);
|
||||
|
||||
_decimationDeduplicator.GetOrStart(hash, async () =>
|
||||
_decimationDeduplicator.GetOrStart(hash, () => _processingQueue.Enqueue(async token =>
|
||||
{
|
||||
await _decimationSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
await _decimationSemaphore.WaitAsync(token).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await DecimateInternalAsync(hash, filePath).ConfigureAwait(false);
|
||||
@@ -69,7 +73,7 @@ public sealed class ModelDecimationService
|
||||
{
|
||||
_decimationSemaphore.Release();
|
||||
}
|
||||
});
|
||||
}, CancellationToken.None));
|
||||
}
|
||||
|
||||
public void ScheduleBatchDecimation(string hash, string filePath, ModelDecimationSettings settings)
|
||||
@@ -89,9 +93,9 @@ public sealed class ModelDecimationService
|
||||
|
||||
_logger.LogInformation("Queued batch model decimation for {Hash}", hash);
|
||||
|
||||
_decimationDeduplicator.GetOrStart(hash, async () =>
|
||||
_decimationDeduplicator.GetOrStart(hash, () => _processingQueue.Enqueue(async token =>
|
||||
{
|
||||
await _decimationSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
await _decimationSemaphore.WaitAsync(token).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await DecimateInternalAsync(hash, filePath, settings, allowExisting: false, destinationOverride: filePath, registerDecimatedPath: false).ConfigureAwait(false);
|
||||
@@ -105,7 +109,7 @@ public sealed class ModelDecimationService
|
||||
{
|
||||
_decimationSemaphore.Release();
|
||||
}
|
||||
});
|
||||
}, CancellationToken.None));
|
||||
}
|
||||
|
||||
public bool ShouldScheduleDecimation(string hash, string filePath, string? gamePath = null)
|
||||
|
||||
19
LightlessSync/Services/ModelProcessingQueue.cs
Normal file
19
LightlessSync/Services/ModelProcessingQueue.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace LightlessSync.Services;
|
||||
|
||||
public sealed class ModelProcessingQueue : IDisposable
|
||||
{
|
||||
private readonly AssetProcessingQueue _queue;
|
||||
|
||||
public ModelProcessingQueue(ILogger<ModelProcessingQueue> logger)
|
||||
{
|
||||
_queue = new AssetProcessingQueue(logger, "LightlessSync.ModelProcessing");
|
||||
}
|
||||
|
||||
public Task Enqueue(Func<CancellationToken, Task> work, CancellationToken token = default)
|
||||
=> _queue.Enqueue(work, token);
|
||||
|
||||
public void Dispose()
|
||||
=> _queue.Dispose();
|
||||
}
|
||||
@@ -8,6 +8,7 @@ using System.Threading;
|
||||
using OtterTex;
|
||||
using OtterImage = OtterTex.Image;
|
||||
using LightlessSync.LightlessConfiguration;
|
||||
using LightlessSync.Services;
|
||||
using LightlessSync.Utils;
|
||||
using LightlessSync.FileCache;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -33,6 +34,7 @@ public sealed class TextureDownscaleService
|
||||
private readonly PlayerPerformanceConfigService _playerPerformanceConfigService;
|
||||
private readonly FileCacheManager _fileCacheManager;
|
||||
private readonly TextureCompressionService _textureCompressionService;
|
||||
private readonly TextureProcessingQueue _processingQueue;
|
||||
|
||||
private readonly TaskRegistry<string> _downscaleDeduplicator = new();
|
||||
private readonly ConcurrentDictionary<string, string> _downscaledPaths = new(StringComparer.OrdinalIgnoreCase);
|
||||
@@ -73,13 +75,15 @@ public sealed class TextureDownscaleService
|
||||
LightlessConfigService configService,
|
||||
PlayerPerformanceConfigService playerPerformanceConfigService,
|
||||
FileCacheManager fileCacheManager,
|
||||
TextureCompressionService textureCompressionService)
|
||||
TextureCompressionService textureCompressionService,
|
||||
TextureProcessingQueue processingQueue)
|
||||
{
|
||||
_logger = logger;
|
||||
_configService = configService;
|
||||
_playerPerformanceConfigService = playerPerformanceConfigService;
|
||||
_fileCacheManager = fileCacheManager;
|
||||
_textureCompressionService = textureCompressionService;
|
||||
_processingQueue = processingQueue;
|
||||
}
|
||||
|
||||
public void ScheduleDownscale(string hash, string filePath, TextureMapKind mapKind)
|
||||
@@ -90,7 +94,7 @@ public sealed class TextureDownscaleService
|
||||
if (!filePath.EndsWith(".tex", StringComparison.OrdinalIgnoreCase)) return;
|
||||
if (_downscaleDeduplicator.TryGetExisting(hash, out _)) return;
|
||||
|
||||
_downscaleDeduplicator.GetOrStart(hash, async () =>
|
||||
_downscaleDeduplicator.GetOrStart(hash, () => _processingQueue.Enqueue(async token =>
|
||||
{
|
||||
TextureMapKind mapKind;
|
||||
try
|
||||
@@ -104,7 +108,7 @@ public sealed class TextureDownscaleService
|
||||
}
|
||||
|
||||
await DownscaleInternalAsync(hash, filePath, mapKind).ConfigureAwait(false);
|
||||
});
|
||||
}, CancellationToken.None));
|
||||
}
|
||||
|
||||
public bool ShouldScheduleDownscale(string filePath)
|
||||
@@ -382,6 +386,12 @@ public sealed class TextureDownscaleService
|
||||
{
|
||||
var isCompressed = sourceFormat.IsCompressed();
|
||||
var targetFormat = isCompressed ? sourceFormat : DXGIFormat.B8G8R8A8UNorm;
|
||||
_logger.LogDebug(
|
||||
"Downscale convert target {TargetFormat} (source {SourceFormat}, compressed {IsCompressed}, penumbraFallback {PenumbraFallback})",
|
||||
targetFormat,
|
||||
sourceFormat,
|
||||
isCompressed,
|
||||
attemptPenumbraFallback);
|
||||
try
|
||||
{
|
||||
result = source.Convert(targetFormat);
|
||||
@@ -433,6 +443,7 @@ public sealed class TextureDownscaleService
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogDebug("Downscale Penumbra re-encode target {Target} for {Hash}.", target, hash);
|
||||
using var uncompressed = resizedScratch.Convert(DXGIFormat.B8G8R8A8UNorm);
|
||||
TexFileHelper.Save(destination, uncompressed);
|
||||
}
|
||||
|
||||
19
LightlessSync/Services/TextureProcessingQueue.cs
Normal file
19
LightlessSync/Services/TextureProcessingQueue.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace LightlessSync.Services;
|
||||
|
||||
public sealed class TextureProcessingQueue : IDisposable
|
||||
{
|
||||
private readonly AssetProcessingQueue _queue;
|
||||
|
||||
public TextureProcessingQueue(ILogger<TextureProcessingQueue> logger)
|
||||
{
|
||||
_queue = new AssetProcessingQueue(logger, "LightlessSync.TextureProcessing");
|
||||
}
|
||||
|
||||
public Task Enqueue(Func<CancellationToken, Task> work, CancellationToken token = default)
|
||||
=> _queue.Enqueue(work, token);
|
||||
|
||||
public void Dispose()
|
||||
=> _queue.Dispose();
|
||||
}
|
||||
@@ -56,6 +56,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
||||
private readonly ModelDecimationService _modelDecimationService;
|
||||
private readonly TextureCompressionService _textureCompressionService;
|
||||
private readonly TextureMetadataHelper _textureMetadataHelper;
|
||||
private readonly TextureProcessingQueue _processingQueue;
|
||||
|
||||
private readonly List<TextureRow> _textureRows = new();
|
||||
private readonly Dictionary<string, TextureCompressionTarget> _textureSelections = new(StringComparer.OrdinalIgnoreCase);
|
||||
@@ -137,7 +138,8 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
||||
LightlessConfigService configService,
|
||||
PlayerPerformanceConfigService playerPerformanceConfig, TransientResourceManager transientResourceManager,
|
||||
TransientConfigService transientConfigService, ModelDecimationService modelDecimationService,
|
||||
TextureCompressionService textureCompressionService, TextureMetadataHelper textureMetadataHelper)
|
||||
TextureCompressionService textureCompressionService, TextureMetadataHelper textureMetadataHelper,
|
||||
TextureProcessingQueue processingQueue)
|
||||
: base(logger, mediator, "Lightless Character Data Analysis", performanceCollectorService)
|
||||
{
|
||||
_characterAnalyzer = characterAnalyzer;
|
||||
@@ -150,6 +152,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
||||
_modelDecimationService = modelDecimationService;
|
||||
_textureCompressionService = textureCompressionService;
|
||||
_textureMetadataHelper = textureMetadataHelper;
|
||||
_processingQueue = processingQueue;
|
||||
Mediator.Subscribe<CharacterDataAnalyzedMessage>(this, (_) =>
|
||||
{
|
||||
_hasUpdate = true;
|
||||
@@ -3716,7 +3719,10 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
||||
_conversionCurrentFileProgress = 0;
|
||||
_conversionFailed = false;
|
||||
|
||||
_conversionTask = RunTextureConversionAsync(requests, _conversionCancellationTokenSource.Token);
|
||||
var conversionToken = _conversionCancellationTokenSource.Token;
|
||||
_conversionTask = _processingQueue.Enqueue(
|
||||
queueToken => RunTextureConversionAsync(requests, queueToken),
|
||||
conversionToken);
|
||||
_showModal = true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user