SHA256
Compare commits
9
Commits
| Author | SHA256 | Date | |
|---|---|---|---|
|
|
ba83935190
|
||
|
|
d2390665af
|
||
|
|
ceb3190274
|
||
|
|
08b13460ae
|
||
|
|
ed7dd5dba4 | ||
|
|
eb648bbe09
|
||
|
|
f1d56f5f9d
|
||
|
|
ed61cef4bb
|
||
|
|
2cfd14e348
|
No files matched your search
@@ -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)"
|
||||
}
|
||||
@@ -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": "(만료됨)"
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
+935
-296
File diff suppressed because it is too large.
Load diff
+33
-31
@@ -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
|
||||
|
||||
Reference in new issue
Block a user