Newer
Older
TIASShot / TIASshot / UI / PreviewProfiler.cs
#if MEMMONITOR
using System.Diagnostics;
using System.Text;
using System.Threading;

namespace TIASshot {

    /// <summary>
    /// プレビュー表示経路の性能計測(MEMMONITOR 限定)。
    /// カメラのコールバックスレッド(Form1.ShowImage)と UI スレッド(Form1_Paint 等)の
    /// 双方から呼ばれるため,すべて Interlocked/lock で加算するスレッド安全なカウンタとする。
    /// 「コピー負荷(コールバックスレッド)」「副モニタの実描画コスト」「GDI/USER ハンドル枯渇」
    /// 「プレビュー画像サイズ肥大」のどれが副モニタ描画崩壊の原因かを切り分けるために,
    /// 各段の呼び出し回数・所要時間・イベント(校正/撮影/F9 トグル)を計測する。
    /// </summary>
    internal static class PreviewProfiler {

        // ---- カウンタ(区間内の呼び出し回数)----------------------------------
        private static long _callbackFrames;   // ShowImage 呼び出し数(コールバックスレッドの実効フレーム数)
        private static long _mainPaints;       // Form1_Paint で実際に picDisplay.Image を差し替えた回数
        private static long _subUpdates;       // PreviewMonitor.UpdateImage 呼び出し数
        private static long _subPaints;        // PreviewMonitor_Paint で実際に 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