11 changed files with 1350 additions and 423 deletions

No files matched your search

+4
View File
@@ -32,5 +32,9 @@
"ui.render_config.validation_title": "Invalid Settings",
"ui.render_config.validation_error": "Please check your input values:\n- Width and Height must be integers > 0\n- Render FPS and Video FPS must be > 0\n- Delay After End must be >= 0",
"ui.render_config.invalid_path": "Output directory path is invalid:\n{0}",
"ui.render_config.planet_colors": "Planet Colors (Optional)",
"ui.render_config.fire_planet": "Fire Planet",
"ui.render_config.ice_planet": "Ice Planet",
"ui.render_config.wind_planet": "Wind Planet",
"ui.render_config.expired": "(Expired)"
}
+4
View File
@@ -32,5 +32,9 @@
"ui.render_config.validation_title": "설정 오류",
"ui.render_config.validation_error": "입력값을 확인해 주세요:\n- 너비와 높이는 0보다 큰 정수여야 합니다.\n- 렌더 FPS와 영상 FPS는 0보다 커야 합니다.\n- 종료 후 대기 시간은 0 이상이어야 합니다.",
"ui.render_config.invalid_path": "출력 디렉토리 경로가 올바르지 않습니다:\n{0}",
"ui.render_config.planet_colors": "행성 색상 설정 (선택 사항)",
"ui.render_config.fire_planet": "불 행성",
"ui.render_config.ice_planet": "얼음 행성",
"ui.render_config.wind_planet": "바람 행성",
"ui.render_config.expired": "(만료됨)"
}
+16
View File
@@ -18,4 +18,20 @@ public static class PersistencePatch
__result = SkipIntroBehavior.Off;
}
}
[HarmonyPostfix]
[HarmonyPatch("GetPlayerColor")]
// ReSharper disable once InconsistentNaming
public static void OverridePlayerColor(bool red, ref PlanetColor __result)
{
if (!RenderManager.Instance || RenderManager.Instance.stage == RenderStage.None) return;
var advConf = RenderManager.Instance.Configuration.AdvancedConfig;
__result = red switch
{
true when advConf.firePlanetColor.HasValue => new PlanetColor(advConf.firePlanetColor.Value),
false when advConf.icePlanetColor.HasValue => new PlanetColor(advConf.icePlanetColor.Value),
_ => __result
};
}
}
-30
View File
@@ -1,30 +0,0 @@
using ADOFAIRenderer.Core;
using HarmonyLib;
using UnityEngine;
namespace ADOFAIRenderer.Patch.Game;
[HarmonyPatch(typeof(PlanetRenderer))]
public static class PlanetRendererPatch
{
[HarmonyPrefix]
[HarmonyPatch("SetColor")]
// ReSharper disable once InconsistentNaming
private static void OverrideColor(PlanetRenderer __instance, ref PlanetColor planetColor)
{
if (RenderManager.Instance.stage == RenderStage.None) return;
var advConf = RenderManager.Instance.Configuration.AdvancedConfig;
ApplyColor(ref planetColor, scrController.instance.planetRed, advConf.firePlanetColor);
ApplyColor(ref planetColor, scrController.instance.planetBlue, advConf.icePlanetColor);
ApplyColor(ref planetColor, scrController.instance.planetGreen, advConf.windPlanetColor);
return;
void ApplyColor(ref PlanetColor planetColor, scrPlanet? targetPlanet, Color? customColor)
{
if (targetPlanet != null && __instance == targetPlanet.planetRenderer && customColor != null)
planetColor = new PlanetColor(customColor.Value);
}
}
}
+35
View File
@@ -0,0 +1,35 @@
using ADOFAIRenderer.Core;
using HarmonyLib;
namespace ADOFAIRenderer.Patch.Game;
[HarmonyPatch(typeof(PlanetarySystem))]
public static class PlanetarySystemPatch
{
[HarmonyPostfix]
[HarmonyPatch("Init")]
// ReSharper disable once InconsistentNaming
public static void OnInit(PlanetarySystem __instance)
{
ApplyWindPlanetColor(__instance);
}
[HarmonyPostfix]
[HarmonyPatch("ApplyMultiplanetColors")]
// ReSharper disable once InconsistentNaming
public static void OnApplyMultiplanetColors(PlanetarySystem __instance)
{
ApplyWindPlanetColor(__instance);
}
private static void ApplyWindPlanetColor(PlanetarySystem planetarySystem)
{
if (RenderManager.Instance == null || RenderManager.Instance.stage == RenderStage.None) return;
var advConf = RenderManager.Instance.Configuration.AdvancedConfig;
if (advConf.windPlanetColor.HasValue && planetarySystem.planetGreen)
{
planetarySystem.planetGreen.planetRenderer.SetColor(new PlanetColor(advConf.windPlanetColor.Value));
}
}
}
-40
View File
@@ -1,40 +0,0 @@
using HarmonyLib;
namespace ADOFAIRenderer.Patch.Game;
[HarmonyPatch(typeof(scrDecoration))]
// ReSharper disable once InconsistentNaming
public static class scrDecorationPatch
{
[HarmonyPrefix]
[HarmonyPatch("SetRotation")]
// ReSharper disable once InconsistentNaming
public static bool SetRotationPrefix(scrDecoration __instance)
{
return __instance != null && __instance && __instance.pivotTrans != null;
}
[HarmonyPrefix]
[HarmonyPatch("SetScale")]
// ReSharper disable once InconsistentNaming
public static bool SetScalePrefix(scrDecoration __instance)
{
return __instance != null && __instance && __instance.pivotTrans != null;
}
[HarmonyPrefix]
[HarmonyPatch("SetOpacity")]
// ReSharper disable once InconsistentNaming
public static bool SetOpacityPrefix(scrDecoration __instance)
{
return __instance != null && __instance;
}
[HarmonyPrefix]
[HarmonyPatch("UpdatePosition")]
// ReSharper disable once InconsistentNaming
public static bool UpdatePositionPrefix(scrDecoration __instance)
{
return __instance != null && __instance && __instance.pivotTrans != null;
}
}
+2
View File
@@ -13,6 +13,8 @@
By decoupling the game's simulation timestep from wall-clock time, ADOFAI Renderer enables creators to produce smooth, ultra-high-definition game videos with perfect audio sync, regardless of hardware limitations or level complexity.
> Generative AI technology was partially utilized in the development of this mod.
---
## ✨ Key Features
+1 -1
View File
@@ -9,7 +9,7 @@ using MelonLoader.Utils;
using UnityEngine;
using Object = UnityEngine.Object;
[assembly: MelonInfo(typeof(RenderMod), "ADOFAI Renderer", "0.2.2-INDEV", "ADOFAI Editor Forum")]
[assembly: MelonInfo(typeof(RenderMod), "ADOFAI Renderer", "0.2.4-INDEV", "ADOFAI Editor Forum")]
[assembly: MelonGame("7th Beat Games", "A Dance of Fire and Ice")]
namespace ADOFAIRenderer;
+320 -25
View File
@@ -8,8 +8,6 @@ using ADOFAIRenderer.Core.Data;
using ADOFAIRenderer.Data;
using ADOFAIRenderer.Localization;
using ADOFAIRenderer.Util;
using ADOFAIRenderer.Util.FFmpeg;
using HarmonyLib;
using MelonLoader;
using O5Kit.Control;
using O5Kit.Core;
@@ -34,8 +32,17 @@ internal static class RenderConfigWindow
private static O5Dropdown<Codec>? _codecDropdown;
private static O5Dropdown<RateControlMode>? _rateControlDropdown;
private static O5InputField? _bitrateInput;
private static TMPro.TextMeshProUGUI? _bitrateLabel;
private static ITweenHandle? _bitrateLabelTween;
private static O5InputField? _maxBitrateInput;
private static TMPro.TextMeshProUGUI? _maxBitrateLabel;
private static ITweenHandle? _maxBitrateLabelTween;
private static O5InputField? _qpInput;
private static TMPro.TextMeshProUGUI? _qpLabel;
private static ITweenHandle? _qpLabelTween;
private static O5Slider? _keyframeIntervalSlider;
private static O5Toggle? _forceSoftwareToggle;
@@ -51,6 +58,17 @@ internal static class RenderConfigWindow
private static O5Toggle? _processAudioToggle;
private static O5Toggle? _nonTileToggle;
private static O5Toggle? _firePlanetToggle;
private static O5ColorPicker? _firePlanetPicker;
private static O5Toggle? _icePlanetToggle;
private static O5ColorPicker? _icePlanetPicker;
private static O5Toggle? _windPlanetToggle;
private static O5ColorPicker? _windPlanetPicker;
private static readonly Color DefaultFirePlanetColor = new(1f, 0f, 0f, 1f); // #FF0000
private static readonly Color DefaultIcePlanetColor = new(0f, 0f, 1f, 1f); // #0000FF
private static readonly Color DefaultWindPlanetColor = new(0.3f, 0.7f, 0f, 1f); // #4DB300
public static void CreateWindow()
{
if (_configWindow == null)
@@ -65,6 +83,7 @@ internal static class RenderConfigWindow
_configWindow.CloseRequested += win =>
{
CloseAllPickers();
win.Rect.gameObject.SetActive(false);
};
@@ -205,14 +224,14 @@ internal static class RenderConfigWindow
c =>
{
config.VideoConfig.Codec = c;
UpdateVideoCodec(c);
UpdateVideoCodecUI(c, config);
});
_bitrateInput = CreateInputField(
ctx, CreateFieldSlot(videoLeft),
LocalizationManager.Get("ui.render_config.bitrate"),
"30000", config.VideoConfig.Bitrate.ToString(),
"input_bitrate",
"input_bitrate", out _bitrateLabel,
TMPro.TMP_InputField.CharacterValidation.Integer,
val => { if (int.TryParse(val, out var b) && b > 0) config.VideoConfig.Bitrate = b; }
);
@@ -221,7 +240,7 @@ internal static class RenderConfigWindow
ctx, CreateFieldSlot(videoLeft),
LocalizationManager.Get("ui.render_config.qp"),
"18", config.VideoConfig.QP.ToString(),
"input_qp",
"input_qp", out _qpLabel,
TMPro.TMP_InputField.CharacterValidation.Integer,
val => { if (int.TryParse(val, out var qp) && qp >= 0) config.VideoConfig.QP = qp; }
);
@@ -233,14 +252,18 @@ internal static class RenderConfigWindow
RateControlMode.CBR, config.VideoConfig.RateControlMode,
rateModes, m => m.ToString(),
"dd_rate_control",
m => config.VideoConfig.RateControlMode = m
m =>
{
config.VideoConfig.RateControlMode = m;
UpdateRateControlUI(m);
}
);
_maxBitrateInput = CreateInputField(
ctx, CreateFieldSlot(videoRight),
LocalizationManager.Get("ui.render_config.max_bitrate"),
"50000", config.VideoConfig.MaxBitrate.ToString(),
"input_max_bitrate",
"input_max_bitrate", out _maxBitrateLabel,
TMPro.TMP_InputField.CharacterValidation.Integer,
val => { if (int.TryParse(val, out var mb) && mb > 0) config.VideoConfig.MaxBitrate = mb; }
);
@@ -250,7 +273,7 @@ internal static class RenderConfigWindow
false, config.VideoConfig.ForceSoftwareEncoder,
LocalizationManager.Get("ui.render_config.force_software"),
"toggle_force_software",
val => config.AudioConfig.RenderAudio = val,
val => config.VideoConfig.ForceSoftwareEncoder = val,
generateLabel: true
);
@@ -436,7 +459,107 @@ internal static class RenderConfigWindow
generateLabel: false
);
// TODO: Fire Planet, Ice Planet, Wind Planet Color Settings (Each Optional)
// 7-1. Planet Color Settings (Optional)
var planetColorsLabel = O5Factory.ControlText(ctx, advContent, 13f, noPad: true);
planetColorsLabel.text = LocalizationManager.Get("ui.render_config.planet_colors");
planetColorsLabel.color = ctx.Theme.TextDim;
// ReSharper disable once InconsistentNaming
var planetLabelLE = planetColorsLabel.gameObject.AddComponent<LayoutElement>();
planetLabelLE.preferredHeight = 18f;
planetLabelLE.minHeight = 18f;
var planetCols = CreateMultiColumnPanel(advContent, 3);
var (fireCol, iceCol, windCol) = (planetCols[0], planetCols[1], planetCols[2]);
var fireSlot = CreateFieldSlot(fireCol);
_firePlanetToggle = CreateToggleField(
ctx, fireSlot, "",
false, config.AdvancedConfig.firePlanetColor.HasValue,
LocalizationManager.Get("ui.render_config.fire_planet"),
"toggle_fire_planet",
val =>
{
_firePlanetPicker?.SetBlocked(!val);
config.AdvancedConfig.firePlanetColor = val ? _firePlanetPicker?.Value : null;
if (!val) _firePlanetPicker?.SetExpanded(false);
},
generateLabel: false
);
_firePlanetPicker = CreateColorPickerField(
ctx, fireSlot,
DefaultFirePlanetColor,
config.AdvancedConfig.firePlanetColor ?? DefaultFirePlanetColor,
"cp_fire_planet",
val =>
{
if (_firePlanetToggle?.Value == true)
config.AdvancedConfig.firePlanetColor = val;
}
);
if (!config.AdvancedConfig.firePlanetColor.HasValue)
{
_firePlanetPicker.SetBlocked(true, true);
}
var iceSlot = CreateFieldSlot(iceCol);
_icePlanetToggle = CreateToggleField(
ctx, iceSlot, "",
false, config.AdvancedConfig.icePlanetColor.HasValue,
LocalizationManager.Get("ui.render_config.ice_planet"),
"toggle_ice_planet",
val =>
{
_icePlanetPicker?.SetBlocked(!val);
config.AdvancedConfig.icePlanetColor = val ? _icePlanetPicker?.Value : null;
if (!val) _icePlanetPicker?.SetExpanded(false);
},
generateLabel: false
);
_icePlanetPicker = CreateColorPickerField(
ctx, iceSlot,
DefaultIcePlanetColor,
config.AdvancedConfig.icePlanetColor ?? DefaultIcePlanetColor,
"cp_ice_planet",
val =>
{
if (_icePlanetToggle?.Value == true)
config.AdvancedConfig.icePlanetColor = val;
}
);
if (!config.AdvancedConfig.icePlanetColor.HasValue)
{
_icePlanetPicker.SetBlocked(true, true);
}
var windSlot = CreateFieldSlot(windCol);
_windPlanetToggle = CreateToggleField(
ctx, windSlot, "",
false, config.AdvancedConfig.windPlanetColor.HasValue,
LocalizationManager.Get("ui.render_config.wind_planet"),
"toggle_wind_planet",
val =>
{
_windPlanetPicker?.SetBlocked(!val);
config.AdvancedConfig.windPlanetColor = val ? _windPlanetPicker?.Value : null;
if (!val) _windPlanetPicker?.SetExpanded(false);
},
generateLabel: false
);
_windPlanetPicker = CreateColorPickerField(
ctx, windSlot,
DefaultWindPlanetColor,
config.AdvancedConfig.windPlanetColor ?? DefaultWindPlanetColor,
"cp_wind_planet",
val =>
{
if (_windPlanetToggle?.Value == true)
config.AdvancedConfig.windPlanetColor = val;
}
);
if (!config.AdvancedConfig.windPlanetColor.HasValue)
{
_windPlanetPicker.SetBlocked(true, true);
}
// 8. Go Render Button
O5Factory.Button(
@@ -465,11 +588,6 @@ internal static class RenderConfigWindow
double Snap(double[] array, double value) =>
array.OrderBy(n => Math.Abs(n - value)).First();
void UpdateVideoCodec(Codec codec)
{
_sampleRateDropdown?.SetValues(CodecUtil.AvailableSampleRate[CodecUtil.AudioCodecMatch[codec]]);
}
}
private static void EnsureDefaultConfig(RenderConfig config)
@@ -511,6 +629,24 @@ internal static class RenderConfigWindow
_randomSeedInput?.Set(config.AdvancedConfig.RandomSeed?.ToString() ?? "", false);
_processAudioToggle?.Set(config.AdvancedConfig.ProcessAudio, false);
_nonTileToggle?.Set(config.AdvancedConfig.NonTile, false);
var fireColor = config.AdvancedConfig.firePlanetColor;
_firePlanetToggle?.Set(fireColor.HasValue, false);
_firePlanetPicker?.Set(fireColor ?? DefaultFirePlanetColor, false);
_firePlanetPicker?.SetBlocked(!fireColor.HasValue, true);
var iceColor = config.AdvancedConfig.icePlanetColor;
_icePlanetToggle?.Set(iceColor.HasValue, false);
_icePlanetPicker?.Set(iceColor ?? DefaultIcePlanetColor, false);
_icePlanetPicker?.SetBlocked(!iceColor.HasValue, true);
var windColor = config.AdvancedConfig.windPlanetColor;
_windPlanetToggle?.Set(windColor.HasValue, false);
_windPlanetPicker?.Set(windColor ?? DefaultWindPlanetColor, false);
_windPlanetPicker?.SetBlocked(!windColor.HasValue, true);
UpdateVideoCodecUI(config.VideoConfig.Codec, config);
UpdateRateControlUI(config.VideoConfig.RateControlMode, noAnimate: true);
}
private static void TryGoRender()
@@ -544,6 +680,17 @@ internal static class RenderConfigWindow
}
}
// Sync Planet Colors
config.AdvancedConfig.firePlanetColor = (_firePlanetToggle?.Value == true && _firePlanetPicker != null)
? _firePlanetPicker.Value
: null;
config.AdvancedConfig.icePlanetColor = (_icePlanetToggle?.Value == true && _icePlanetPicker != null)
? _icePlanetPicker.Value
: null;
config.AdvancedConfig.windPlanetColor = (_windPlanetToggle?.Value == true && _windPlanetPicker != null)
? _windPlanetPicker.Value
: null;
// Sync Output Directory
if (_outputDirInput != null)
{
@@ -588,16 +735,18 @@ internal static class RenderConfigWindow
);
}
// 카드(또는 상단 섹션)당 하나의 좌/우 컨테이너를 만들어
// 가로 한 줄 두 필드 사이 중앙이 행마다 어긋나지 않도록 한다.
// - 패널 1개 = 중앙선 1개 (행마다 HLG를 만들던 기존 방식의 P1-P2 어긋남 방지)
// - 좌/우 컬럼은 preferredWidth=0 + flexibleWidth=1 로 항상 50:50
// 카드(또는 상단 섹션)당 하나의 다열 컨테이너를 만들어
// 가로 한 줄 각 필드 사이 중앙이 행마다 어긋나지 않도록 한다.
// - 패널 1개 = 균등 분할 (행마다 HLG를 만들던 기존 방식의 어긋남 방지)
// - 각 컬럼은 preferredWidth=0 + flexibleWidth=1 로 항상 1:1:...:1 균등 분할
// - 각 필드는 Field 슬롯(label 18 + 간격 3 + 컨트롤 50)으로 높이를 고정해
// 좌/우 스택을 써도 행 단위 Y 정렬이 유지되도록 한다.
private static (RectTransform left, RectTransform right) CreateTwoColumnPanel(
Transform parent, float spacing = 8f, float columnSpacing = 8f)
private static RectTransform[] CreateMultiColumnPanel(
Transform parent, int columnCount, float spacing = 8f, float columnSpacing = 8f)
{
var panelObj = new GameObject("TwoColPanel");
if (columnCount < 1) throw new ArgumentOutOfRangeException(nameof(columnCount), "Column count must be at least 1.");
var panelObj = new GameObject($"{columnCount}ColPanel");
panelObj.transform.SetParent(parent, false);
panelObj.AddComponent<RectTransform>();
@@ -615,10 +764,20 @@ internal static class RenderConfigWindow
var panelLE = panelObj.AddComponent<LayoutElement>();
panelLE.flexibleWidth = 1f;
var left = CreateColumn(panelObj.transform, "LeftCol", columnSpacing);
var right = CreateColumn(panelObj.transform, "RightCol", columnSpacing);
var columns = new RectTransform[columnCount];
for (var i = 0; i < columnCount; i++)
{
columns[i] = CreateColumn(panelObj.transform, $"Col{i + 1}", columnSpacing);
}
return (left, right);
return columns;
}
private static (RectTransform left, RectTransform right) CreateTwoColumnPanel(
Transform parent, float spacing = 8f, float columnSpacing = 8f)
{
var cols = CreateMultiColumnPanel(parent, 2, spacing, columnSpacing);
return (cols[0], cols[1]);
}
private static RectTransform CreateColumn(Transform parent, string name, float spacing)
@@ -678,7 +837,21 @@ internal static class RenderConfigWindow
TMPro.TMP_InputField.CharacterValidation characterValidation = TMPro.TMP_InputField.CharacterValidation.None,
Action<string>? onChanged = null)
{
var label = O5Factory.ControlText(ctx, colParent, 13f, noPad: true);
return CreateInputField(ctx, colParent, labelText, defaultValue, value, id, out _, characterValidation, onChanged);
}
private static O5InputField CreateInputField(
O5Context ctx,
Transform colParent,
string labelText,
string? defaultValue,
string value,
string id,
out TMPro.TextMeshProUGUI label,
TMPro.TMP_InputField.CharacterValidation characterValidation = TMPro.TMP_InputField.CharacterValidation.None,
Action<string>? onChanged = null)
{
label = O5Factory.ControlText(ctx, colParent, 13f, noPad: true);
label.text = labelText;
label.color = ctx.Theme.TextDim;
@@ -702,6 +875,57 @@ internal static class RenderConfigWindow
return inputField;
}
private static void SetFieldBlocked(
O5InputField? input,
TMPro.TextMeshProUGUI? label,
ref ITweenHandle? tweenHandle,
bool blocked,
bool noAnimate = false)
{
input?.SetBlocked(blocked, noAnimate);
if (label == null) return;
tweenHandle?.Kill();
var ctx = RendererUIManager.Context;
var baseColor = ctx.Theme.TextDim;
var targetColor = blocked
? new Color(baseColor.r, baseColor.g, baseColor.b, baseColor.a * 0.4f)
: baseColor;
if (noAnimate)
{
label.color = targetColor;
}
else
{
tweenHandle = ctx.Tween.TweenColor(label, targetColor, 0.2f);
}
}
private static void UpdateRateControlUI(RateControlMode mode, bool noAnimate = false)
{
bool blockBitrate = mode is RateControlMode.CQP;
bool blockMaxBitrate = mode is RateControlMode.CBR or RateControlMode.CQP;
bool blockQp = mode is RateControlMode.CBR or RateControlMode.VBR;
SetFieldBlocked(_bitrateInput, _bitrateLabel, ref _bitrateLabelTween, blockBitrate, noAnimate);
SetFieldBlocked(_maxBitrateInput, _maxBitrateLabel, ref _maxBitrateLabelTween, blockMaxBitrate, noAnimate);
SetFieldBlocked(_qpInput, _qpLabel, ref _qpLabelTween, blockQp, noAnimate);
}
private static void UpdateVideoCodecUI(Codec codec, RenderConfig? config = null)
{
if (_sampleRateDropdown == null) return;
_sampleRateDropdown.SetValues(CodecUtil.AvailableSampleRate[CodecUtil.AudioCodecMatch[codec]]);
if (config != null && _sampleRateDropdown.Value is var currentRate)
{
config.AudioConfig.AudioSampleRate = currentRate;
}
}
private static O5InputField CreateFullWidthInputField(
O5Context ctx,
Transform parent,
@@ -819,8 +1043,79 @@ internal static class RenderConfigWindow
);
}
private static O5ColorPicker CreateColorPickerField(
O5Context ctx,
Transform colParent,
Color? defaultValue,
Color value,
string id,
Action<Color>? onChanged = null)
{
var cpContainer = new GameObject($"CP_{id}");
cpContainer.transform.SetParent(colParent, false);
// ReSharper disable once InconsistentNaming
var cpLE = cpContainer.AddComponent<LayoutElement>();
var height = ctx.Theme.ControlHeightFor(ctx.Theme.FontSizeBody);
cpLE.preferredHeight = height;
cpLE.minHeight = height;
var canvasRect = RendererUIManager.Instance.WindowManager.Canvas.Root.GetComponent<RectTransform>();
var picker = O5Factory.ColorPicker(
ctx: ctx,
parent: cpContainer.transform,
canvasRect: canvasRect,
canvasCamera: null,
defaultValue: defaultValue,
value: value,
onChanged: onChanged,
onComplete: onChanged,
id: id
);
// 3열 등 다열 레이아웃 환경에 맞춰 O5ColorPicker 헤더 내부 비율 및 폰트 크기 조정
// O5Kit 기본값(Preview: 0~0.5, HexInput: 0.62~1.0, fontSize: 19)은 너비가 좁을 때
// Hex 문자열(8자리 RGBA) 앞부분이 잘려 보이는 현상이 발생하므로 공간을 재분배합니다.
if (picker.Rect.Find("Header") is RectTransform header)
{
if (header.Find("Preview") is RectTransform preview)
{
preview.anchorMax = new Vector2(0.28f, 1f);
}
if (header.Find("ToggleArea") is RectTransform toggleArea)
{
toggleArea.anchorMax = new Vector2(0.30f, 1f);
}
if (header.Find("HexInput") is RectTransform hexInput)
{
hexInput.anchorMin = new Vector2(0.32f, 0f);
var tmpInput = hexInput.GetComponentInChildren<TMPro.TMP_InputField>();
if (tmpInput != null && tmpInput.textComponent != null)
{
tmpInput.textComponent.fontSize = 15f;
}
}
}
return picker;
}
private static void SetWindowActive(bool enabled)
{
if (!enabled)
{
CloseAllPickers();
}
_configWindow?.Rect.gameObject.SetActive(enabled);
}
private static void CloseAllPickers()
{
_firePlanetPicker?.SetExpanded(false, true);
_icePlanetPicker?.SetExpanded(false, true);
_windPlanetPicker?.SetExpanded(false, true);
}
}
File diff suppressed because it is too large. Load diff
+33 -31
View File
@@ -26,12 +26,34 @@ namespace ADOFAIRenderer.Util
private readonly object _poolLock = new();
private bool _isInitialized;
private volatile bool _isDisposed;
private readonly AVPixelFormat _targetFormat;
private readonly ComputeShader? _computeShader;
private readonly int _kernelIndex = -1;
private readonly bool _useGPUConversion;
private readonly AVPixelFormat _expectedFormat;
public FrameReader(AVPixelFormat targetFormat)
{
_targetFormat = targetFormat;
_computeShader = ModAssets.ColorConversionShader;
if (_computeShader)
{
var kernelName = targetFormat switch
{
AVPixelFormat.AV_PIX_FMT_NV12 => "CSMain_NV12",
AVPixelFormat.AV_PIX_FMT_YUV420P => "CSMain_YUV420P",
_ => null
};
if (kernelName != null)
{
try { _kernelIndex = _computeShader.FindKernel(kernelName); }
catch { _kernelIndex = -1; }
}
}
_useGPUConversion = _kernelIndex >= 0;
_expectedFormat = _useGPUConversion ? targetFormat : AVPixelFormat.AV_PIX_FMT_RGBA;
InitializePool();
}
@@ -71,42 +93,22 @@ namespace ADOFAIRenderer.Util
state.IsInFlight = true;
}
// Check if GPU color conversion is available
var expectedFmt = AVPixelFormat.AV_PIX_FMT_RGBA;
var kernelIndex = -1;
if (ModAssets.ColorConversionShader)
{
var kernalName = _targetFormat switch
{
AVPixelFormat.AV_PIX_FMT_NV12 => "CSMain_NV12",
AVPixelFormat.AV_PIX_FMT_YUV420P => "CSMain_YUV420P",
_ => null
};
if (kernalName != null)
kernelIndex = ModAssets.ColorConversionShader.FindKernel(kernalName);
}
var useGPUConversion = kernelIndex >= 0;
if (useGPUConversion) expectedFmt = _targetFormat;
var bufferSize = texture.width * texture.height * 4; // RGBA = 4 channel
state.Setup(currentFrame, bufferSize, useGPUConversion, expectedFmt, texture.width, texture.height);
state.Setup(currentFrame, bufferSize, _useGPUConversion, _expectedFormat, texture.width, texture.height);
if (useGPUConversion && state.GPUBuffer != null && ModAssets.ColorConversionShader)
if (_useGPUConversion && state.GPUBuffer != null && _computeShader)
{
ModAssets.ColorConversionShader.SetTexture(kernelIndex, SourceTexture, texture);
ModAssets.ColorConversionShader.SetBuffer(kernelIndex, DestinationBuffer, state.GPUBuffer);
ModAssets.ColorConversionShader.SetInt(Width, texture.width);
ModAssets.ColorConversionShader.SetInt(Height, texture.height);
_computeShader.SetTexture(_kernelIndex, SourceTexture, texture);
_computeShader.SetBuffer(_kernelIndex, DestinationBuffer, state.GPUBuffer);
_computeShader.SetInt(Width, texture.width);
_computeShader.SetInt(Height, texture.height);
var threadGroupsX = expectedFmt == AVPixelFormat.AV_PIX_FMT_NV12
var threadGroupsX = _expectedFormat == AVPixelFormat.AV_PIX_FMT_NV12
? (texture.width + 31) / 32
: (texture.width + 63) / 64;
var threadGroupsY = (texture.height + 15) / 16;
ModAssets.ColorConversionShader.Dispatch(kernelIndex, threadGroupsX, threadGroupsY, 1);
_computeShader.Dispatch(_kernelIndex, threadGroupsX, threadGroupsY, 1);
AsyncGPUReadback.RequestIntoNativeArray(
ref state.NativeBuffer, state.GPUBuffer, state.CachedCallback