#if MEMMONITOR
using System.Diagnostics;
using System.Text;
using System.Threading;
namespace TIASshot {
/// <summary>
/// プレビュー表示経路の性能計測(MEMMONITOR 限定)。
/// カメラのコールバックスレッド(Form1.ShowImage)と UI スレッド(MainRefreshTimer_Tick 等)の
/// 双方から呼ばれるため,すべて Interlocked/lock で加算するスレッド安全なカウンタとする。
/// 「コピー負荷(コールバックスレッド)」「副モニタの実描画コスト」「GDI/USER ハンドル枯渇」
/// 「プレビュー画像サイズ肥大」のどれが副モニタ描画崩壊の原因かを切り分けるために,
/// 各段の呼び出し回数・所要時間・イベント(校正/撮影/F9 トグル)を計測する。
/// </summary>
internal static class PreviewProfiler {
// ---- カウンタ(区間内の呼び出し回数)----------------------------------
private static long _callbackFrames; // ShowImage 呼び出し数(コールバックスレッドの実効フレーム数)
private static long _mainPaints; // MainRefreshTimer_Tick で実際に picDisplay.Image を差し替えた回数
private static long _subUpdates; // PreviewMonitor.UpdateImage 呼び出し数
private static long _subPaints; // RefreshTimer_Tick で実際に picPreview.Image を差し替えた回数
private static long _subDrops; // UpdateImage で旧バッファを取りこぼし Dispose した回数
// ---- コピー時間累積(Stopwatch tick)----------------------------------
private static long _mainCopyTicks;
private static long _mainCopySamples;
private static long _subCopyTicks;
private static long _subCopySamples;
// ---- 副モニタの実描画時間累積(Stopwatch tick)--------------------------
// TimedPictureBox.OnPaint(picPreview の実際の GDI 描画: base.OnPaint)の所要時間。
// 「描画コストそのものが増えているか」と「メッセージ枯渇で呼び出し自体が減っているか」を
// sub_paint_fps(呼び出しレート)と組み合わせて切り分けるための指標。
private static long _subPaintTicks;
private static long _subPaintSamples;
// ---- 直近の副モニタ表示状態 --------------------------------------------
private static int _isSubShown; // 0/1 を Interlocked で扱う(bool は Interlocked 非対応のため)
public static bool IsSubShown {
get { return Interlocked.CompareExchange(ref _isSubShown, 0, 0) != 0; }
set { Interlocked.Exchange(ref _isSubShown, value ? 1 : 0); }
}
// ---- 直近の表示画像サイズ(プレビュー画像肥大の確認用)--------------------
private static int _lastImgW;
private static int _lastImgH;
/// <summary>Form1.ShowImage から呼ばれ,直近に表示したフレームのサイズを記録する</summary>
public static void SetLastImageSize(int w, int h) {
Interlocked.Exchange(ref _lastImgW, w);
Interlocked.Exchange(ref _lastImgH, h);
}
// ---- イベント(CSV の event 列用)----------------------------------------
// F9 トグル(SUB_ON/SUB_OFF)・校正(CALIB_START/CALIB_END)・撮影(SHOT_START/SHOT_END)を
// 文字列イベントとして記録する。同一 500ms 区間内に複数発生した場合は ";" で連結する。
// 呼び出し元(コールバックスレッド・UI スレッド・撮影スレッド)が異なるため lock で保護する。
private static readonly object _eventLock = new object();
private static readonly StringBuilder _pendingEvents = new StringBuilder();
/// <summary>イベントタグを保留イベントに追加する(スレッドセーフ)</summary>
public static void MarkEvent(string tag) {
lock (_eventLock) {
if (_pendingEvents.Length > 0) _pendingEvents.Append(';');
_pendingEvents.Append(tag);
}
}
/// <summary>保留中のイベント文字列を読み取ってクリアする(無ければ "")</summary>
public static string ConsumeEvents() {
lock (_eventLock) {
string result = _pendingEvents.ToString();
_pendingEvents.Clear();
return result;
}
}
// ---- カウンタ加算ヘルパ -------------------------------------------------
public static void IncrementCallbackFrames() => Interlocked.Increment(ref _callbackFrames);
public static void IncrementMainPaints() => Interlocked.Increment(ref _mainPaints);
public static void IncrementSubUpdates() => Interlocked.Increment(ref _subUpdates);
public static void IncrementSubPaints() => Interlocked.Increment(ref _subPaints);
public static void IncrementSubDrops() => Interlocked.Increment(ref _subDrops);
/// <summary>本モニタ用コピー(dispCopy 生成)の所要時間を記録する</summary>
public static void AddMainCopyTicks(long ticks) {
Interlocked.Add(ref _mainCopyTicks, ticks);
Interlocked.Increment(ref _mainCopySamples);
}
/// <summary>副モニタ用コピー(new Bitmap(bmp))の所要時間を記録する</summary>
public static void AddSubCopyTicks(long ticks) {
Interlocked.Add(ref _subCopyTicks, ticks);
Interlocked.Increment(ref _subCopySamples);
}
/// <summary>TimedPictureBox(picPreview)の実描画(base.OnPaint)所要時間を記録する</summary>
public static void AddSubPaintTicks(long ticks) {
Interlocked.Add(ref _subPaintTicks, ticks);
Interlocked.Increment(ref _subPaintSamples);
}
// ---- スナップショット ----------------------------------------------------
/// <summary>
/// 区間計測結果のスナップショット。呼び出し回数はそのまま(呼び出し側で区間秒で割って FPS 換算する),
/// コピー時間・描画時間は ms 平均に変換済み。
/// </summary>
public struct ProfilerSnapshot {
public long CallbackFrames;
public long MainPaints;
public long SubUpdates;
public long SubPaints;
public long SubDrops;
public double MainCopyMs; // 平均コピー時間(ms)。サンプルなしなら 0
public double SubCopyMs; // 平均コピー時間(ms)。サンプルなしなら 0
public double SubPaintMs; // picPreview の平均実描画時間(ms)。サンプルなしなら 0
public bool SubShown;
public int ImgW; // 直近に表示したフレームの幅
public int ImgH; // 直近に表示したフレームの高さ
}
/// <summary>
/// 現在値を読み取ってゼロにリセットし,区間スナップショットを返す。
/// MemoryMonitorForm の Timer_Tick(500ms 間隔)から呼ばれる想定。
/// </summary>
public static ProfilerSnapshot Snapshot() {
long callbackFrames = Interlocked.Exchange(ref _callbackFrames, 0);
long mainPaints = Interlocked.Exchange(ref _mainPaints, 0);
long subUpdates = Interlocked.Exchange(ref _subUpdates, 0);
long subPaints = Interlocked.Exchange(ref _subPaints, 0);
long subDrops = Interlocked.Exchange(ref _subDrops, 0);
long mainCopyTicks = Interlocked.Exchange(ref _mainCopyTicks, 0);
long mainCopySamples = Interlocked.Exchange(ref _mainCopySamples, 0);
long subCopyTicks = Interlocked.Exchange(ref _subCopyTicks, 0);
long subCopySamples = Interlocked.Exchange(ref _subCopySamples, 0);
long subPaintTicks = Interlocked.Exchange(ref _subPaintTicks, 0);
long subPaintSamples = Interlocked.Exchange(ref _subPaintSamples, 0);
double tickToMs = 1000.0 / Stopwatch.Frequency;
return new ProfilerSnapshot {
CallbackFrames = callbackFrames,
MainPaints = mainPaints,
SubUpdates = subUpdates,
SubPaints = subPaints,
SubDrops = subDrops,
MainCopyMs = mainCopySamples > 0 ? (mainCopyTicks * tickToMs) / mainCopySamples : 0.0,
SubCopyMs = subCopySamples > 0 ? (subCopyTicks * tickToMs) / subCopySamples : 0.0,
SubPaintMs = subPaintSamples > 0 ? (subPaintTicks * tickToMs) / subPaintSamples : 0.0,
SubShown = IsSubShown,
ImgW = Interlocked.CompareExchange(ref _lastImgW, 0, 0),
ImgH = Interlocked.CompareExchange(ref _lastImgH, 0, 0),
};
}
}
}
#endif