diff --git a/Core/RenderManager.cs b/Core/RenderManager.cs index b6c7d7c..7dca0a1 100644 --- a/Core/RenderManager.cs +++ b/Core/RenderManager.cs @@ -466,6 +466,8 @@ namespace ADOFAIRenderer.Core TargetBitrate = Configuration.EncodingConfig.Bitrate, MaxBitrate = Configuration.EncodingConfig.MaxBitrate, GopSize = Configuration.EncodingConfig.KeyframeInterval + GopSize = Configuration.EncodingConfig.KeyframeInterval, + ForceSoftware = Configuration.EncodingConfig.ForceSoftwareEncoder }; videoFFmpeg = new FFmpegNativeEncoder(settings); diff --git a/Data/RenderConfig.cs b/Data/RenderConfig.cs index 09744c2..2de90c3 100644 --- a/Data/RenderConfig.cs +++ b/Data/RenderConfig.cs @@ -29,5 +29,6 @@ namespace ADOFAIRenderer.Data // ReSharper disable once InconsistentNaming public int QP; public double KeyframeInterval; + public bool ForceSoftwareEncoder; } } \ No newline at end of file diff --git a/UI/RenderConfigUIManager.cs b/UI/RenderConfigUIManager.cs index f162301..14a3835 100644 --- a/UI/RenderConfigUIManager.cs +++ b/UI/RenderConfigUIManager.cs @@ -1,4 +1,3 @@ -using System.Text; using ADOFAIRenderer.Core; using ADOFAIRenderer.Data; using ADOFAIRenderer.Util.FFmpeg; @@ -103,7 +102,8 @@ namespace ADOFAIRenderer.UI Bitrate = config.EncodingConfig.Bitrate, MaxBitrate = config.EncodingConfig.MaxBitrate, QP = config.EncodingConfig.QP, - KeyframeInterval = config.EncodingConfig.KeyframeInterval + KeyframeInterval = config.EncodingConfig.KeyframeInterval, + ForceSoftwareEncoder = config.EncodingConfig.ForceSoftwareEncoder }; if (_root.Contains(_encodingConfigContainer)) @@ -203,6 +203,14 @@ namespace ADOFAIRenderer.UI keyframeRow.Add(keyframeField); _encodingConfigContainer.Add(keyframeRow); + // 6.75. 강제 소프트웨어 인코더 로우 (라벨 + 토글) + var forceSoftwareRow = CreateRow(); + var forceSoftwareLabel = CreateFormLabel("소프트웨어 인코더"); + var forceSoftwareToggle = new Toggle { name = "ForceSoftwareEncoder", label = "", value = tempConfig.ForceSoftwareEncoder }; + forceSoftwareRow.Add(forceSoftwareLabel); + forceSoftwareRow.Add(forceSoftwareToggle); + _encodingConfigContainer.Add(forceSoftwareRow); + // 7. QP = 0 (무손실) 경고 라벨 로우 생성 (ui-row 클래스를 상속받아 NanumSquareNeo 폰트가 적용되도록 처리) var warningRow = CreateRow(); warningRow.style.height = StyleKeyword.Auto; @@ -240,10 +248,10 @@ namespace ADOFAIRenderer.UI { if (System.Enum.TryParse(selectedModeStr, out var mode)) { - bitrateRow.style.display = (mode == RateControlMode.CBR || mode == RateControlMode.VBR) ? DisplayStyle.Flex : DisplayStyle.None; - maxBitrateRow.style.display = (mode == RateControlMode.VBR) ? DisplayStyle.Flex : DisplayStyle.None; - qpRow.style.display = (mode == RateControlMode.CQP) ? DisplayStyle.Flex : DisplayStyle.None; - warningRow.style.display = (mode == RateControlMode.CQP && qpField.value == 0) ? DisplayStyle.Flex : DisplayStyle.None; + bitrateRow.style.display = mode is RateControlMode.CBR or RateControlMode.VBR ? DisplayStyle.Flex : DisplayStyle.None; + maxBitrateRow.style.display = mode == RateControlMode.VBR ? DisplayStyle.Flex : DisplayStyle.None; + qpRow.style.display = mode == RateControlMode.CQP ? DisplayStyle.Flex : DisplayStyle.None; + warningRow.style.display = mode == RateControlMode.CQP && qpField.value == 0 ? DisplayStyle.Flex : DisplayStyle.None; } } @@ -292,6 +300,7 @@ namespace ADOFAIRenderer.UI tempConfig.MaxBitrate = maxBitrateField.value; tempConfig.QP = qpField.value; tempConfig.KeyframeInterval = keyframeField.value; + tempConfig.ForceSoftwareEncoder = forceSoftwareToggle.value; if (tempConfig.RateControlMode == RateControlMode.VBR && tempConfig.MaxBitrate < tempConfig.Bitrate) { @@ -362,7 +371,8 @@ namespace ADOFAIRenderer.UI { RateControlMode = RateControlMode.CBR, Bitrate = 30000, MaxBitrate = 50000, - QP = 18, KeyframeInterval = 2.0 + QP = 18, KeyframeInterval = 2.0, + ForceSoftwareEncoder = false }; } } diff --git a/Util/FFmpeg/FFmpegNativeEncoder.cs b/Util/FFmpeg/FFmpegNativeEncoder.cs index c7c3477..3339613 100644 --- a/Util/FFmpeg/FFmpegNativeEncoder.cs +++ b/Util/FFmpeg/FFmpegNativeEncoder.cs @@ -68,6 +68,9 @@ namespace ADOFAIRenderer.Util.FFmpeg /// GOP(Group of Pictures) 크기 (초). 키프레임 간격을 의미합니다. public double GopSize; + + /// 소프트웨어 CPU 인코더를 강제 사용할지 여부. + public bool ForceSoftware; } // ────────────────────────────────────────────────────────────── @@ -214,7 +217,7 @@ namespace ADOFAIRenderer.Util.FFmpeg /// private string[] GetEncoderCandidates() { - return GPUUtil.GetPrioritizedEncoderCandidates(_settings.Codec).ToArray(); + return GPUUtil.GetPrioritizedEncoderCandidates(_settings.Codec, _settings.ForceSoftware).ToArray(); } /// diff --git a/Util/GPUUtil.cs b/Util/GPUUtil.cs index 6ac8e28..11dcb0b 100644 --- a/Util/GPUUtil.cs +++ b/Util/GPUUtil.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Text.RegularExpressions; using ADOFAIRenderer.Util.FFmpeg; using MelonLoader; @@ -130,12 +131,18 @@ namespace ADOFAIRenderer.Util return null; } - public static List GetPrioritizedEncoderCandidates(Codec codec) + public static List GetPrioritizedEncoderCandidates(Codec codec, bool forceSoftware = false) { if (!CodecMap.TryGetValue(codec, out var list)) return new List(); - var vendor = GetActiveGPUVendor(); var prioritized = new List(); + if (forceSoftware) + { + prioritized.AddRange(from item in list where item.Item1 == GPUVendor.Unknown select item.Item2); + return prioritized; + } + + var vendor = GetActiveGPUVendor(); var others = new List(); foreach (var item in list)