-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathfull_diff.patch
More file actions
5923 lines (5407 loc) · 330 KB
/
full_diff.patch
File metadata and controls
5923 lines (5407 loc) · 330 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
diff --git a/QuickView/HeavyLanePool.cpp b/QuickView/HeavyLanePool.cpp
index 4b031a2..119b0cd 100644
--- a/QuickView/HeavyLanePool.cpp
+++ b/QuickView/HeavyLanePool.cpp
@@ -21,6 +21,8 @@ namespace {
HeavyLanePool* pool;
std::wstring path;
ImageID id;
+ PaneSlot targetSlot;
+ uint64_t generationId;
};
struct MmfDeleterCtx {
@@ -541,7 +543,7 @@ void HeavyLanePool::ShrinkMemory() {
// Task Submission
// ============================================================================
-void HeavyLanePool::Submit(const std::wstring& path, ImageID imageId, std::shared_ptr<QuickView::MappedFile> mmf) {
+void HeavyLanePool::Submit(const std::wstring& path, ImageID imageId, std::shared_ptr<QuickView::MappedFile> mmf, PaneSlot targetSlot, uint64_t generationId) {
// [Hardware] Update IO throttling based on target drive
bool isSSD = SystemInfo::IsSolidStateDrive(path);
UpdateIOLimit(isSSD ? m_cap : 2);
@@ -553,7 +555,7 @@ void HeavyLanePool::Submit(const std::wstring& path, ImageID imageId, std::share
// [Revision 2] Trigger ONLY for the active image. Prefetch jobs (ID mismatch)
// are prohibited from destroying current Titan resources.
if (m_isTitanMode.load(std::memory_order_relaxed) && imageId == m_activeTitanImageId.load(std::memory_order_relaxed)) {
- EnsureMasterWarmup(path, imageId, mmf);
+ EnsureMasterWarmup(path, imageId, mmf, targetSlot, generationId);
}
std::lock_guard lock(m_poolMutex);
@@ -564,7 +566,7 @@ void HeavyLanePool::Submit(const std::wstring& path, ImageID imageId, std::share
// [Dedup] Prevent redundant decoding jobs in the pending queue
if (!m_isTitanMode) {
for (const auto& existing : m_pendingJobs) {
- if (existing.type == JobType::Standard && existing.imageId == imageId) {
+ if (existing.type == JobType::Standard && existing.imageId == imageId && existing.targetSlot == targetSlot) {
// If the generation is old, we could update it. But simple dedup is fine.
// Just return if we already have it pending.
return;
@@ -576,6 +578,8 @@ void HeavyLanePool::Submit(const std::wstring& path, ImageID imageId, std::share
job.type = JobType::Standard;
job.path = path;
job.imageId = imageId;
+ job.targetSlot = targetSlot;
+ job.generationId = generationId;
job.submitTime = std::chrono::steady_clock::now();
job.mmf = mmf;
job.targetHdrHeadroomStops = m_targetHdrHeadroomStops.load(std::memory_order_relaxed);
@@ -590,13 +594,15 @@ void HeavyLanePool::Submit(const std::wstring& path, ImageID imageId, std::share
m_poolCv.notify_all(); // [Fix] notify_all required
}
-void HeavyLanePool::SubmitFullDecode(const std::wstring& path, ImageID imageId, std::shared_ptr<QuickView::MappedFile> mmf) {
+void HeavyLanePool::SubmitFullDecode(const std::wstring& path, ImageID imageId, std::shared_ptr<QuickView::MappedFile> mmf, PaneSlot targetSlot, uint64_t generationId) {
std::lock_guard lock(m_poolMutex);
JobInfo job;
job.type = JobType::Standard;
job.path = path;
job.imageId = imageId;
+ job.targetSlot = targetSlot;
+ job.generationId = generationId;
job.submitTime = std::chrono::steady_clock::now();
job.mmf = mmf;
job.targetHdrHeadroomStops = m_targetHdrHeadroomStops.load(std::memory_order_relaxed);
@@ -733,14 +739,14 @@ void HeavyLanePool::SubmitTileBatch(const std::wstring& path, ImageID imageId, s
// Cancellation
// ============================================================================
-void HeavyLanePool::CancelOthers(ImageID currentId) {
+void HeavyLanePool::CancelOthers(ImageID currentId, PaneSlot targetSlot) {
std::lock_guard lock(m_poolMutex);
// 1. Clear Job Queue of non-matching IDs
auto it = m_pendingJobs.begin();
int removedTiles = 0;
while (it != m_pendingJobs.end()) {
- if (it->imageId != currentId) {
+ if (it->targetSlot == targetSlot && it->imageId != currentId) {
if (it->type == JobType::Tile) {
removedTiles++;
// [Dedup] Remove from in-flight set
@@ -1340,7 +1346,7 @@ void HeavyLanePool::PerformDecode(int workerId, const JobInfo& job, std::stop_to
// For WIC formats (TIFF, AVIF, etc), loading from MMF via SHCreateMemStream COPIES the file,
// leading to massive memory bloat/OOM for 1GB+ large files. Pass directly to file loader instead.
{
- auto* ctx = new(std::nothrow) AuxLayerReadyCtx{ this, job.path, job.imageId };
+ auto* ctx = new(std::nothrow) AuxLayerReadyCtx{ this, job.path, job.imageId, job.targetSlot, job.generationId };
if (ctx) {
rawFrame.onAuxLayerReady.ctx = ctx;
rawFrame.onAuxLayerReady.pfn = [](void* c, std::unique_ptr<QuickView::AuxLayer> aux, QuickView::GpuBlendOp op, QuickView::GpuShaderPayload payload) {
@@ -1349,6 +1355,8 @@ void HeavyLanePool::PerformDecode(int workerId, const JobInfo& job, std::stop_to
ev.type = EventType::AuxLayerReady;
ev.filePath = lctx->path;
ev.imageId = lctx->id;
+ ev.targetSlot = lctx->targetSlot;
+ ev.generationId = lctx->generationId;
ev.auxLayer = std::move(aux);
ev.blendOp = op;
ev.shaderPayload = payload;
@@ -1779,6 +1787,8 @@ tile_decode_done: ; // [P14] Jump target for fast path (skip legacy TJ decode)
EngineEvent evt;
evt.filePath = job.path;
evt.imageId = job.imageId;
+ evt.targetSlot = job.targetSlot;
+ evt.generationId = job.generationId;
if (job.type == JobType::Tile) {
evt.type = EventType::TileReady;
@@ -1895,6 +1905,8 @@ tile_decode_done: ; // [P14] Jump target for fast path (skip legacy TJ decode)
evt.type = EventType::LoadError;
evt.filePath = job.path;
evt.imageId = job.imageId;
+ evt.targetSlot = job.targetSlot;
+ evt.generationId = job.generationId;
evt.hr = hr;
QueueResult(std::move(evt));
}
@@ -2115,7 +2127,7 @@ void HeavyLanePool::StopMasterWarmup() {
m_lodCacheCond.notify_all(); // Wake any waiters so they can re-check
}
-void HeavyLanePool::EnsureMasterWarmup(const std::wstring& path, ImageID imageId, std::shared_ptr<QuickView::MappedFile> mmf) {
+void HeavyLanePool::EnsureMasterWarmup(const std::wstring& path, ImageID imageId, std::shared_ptr<QuickView::MappedFile> mmf, PaneSlot targetSlot, uint64_t generationId) {
if (!ShouldWarmupMasterBacking()) return;
if (!mmf || !mmf->IsValid()) return;
@@ -2135,7 +2147,7 @@ void HeavyLanePool::EnsureMasterWarmup(const std::wstring& path, ImageID imageId
m_masterWarmupReady.store(false, std::memory_order_release);
const uint32_t warmupGen = m_generationID.load(std::memory_order_acquire);
- m_masterWarmupThread = std::jthread([this, path, imageId, mmf, warmupGen](std::stop_token st) {
+ m_masterWarmupThread = std::jthread([this, path, imageId, mmf, warmupGen, targetSlot, generationId](std::stop_token st) {
// [Fix] Ensure the UI marquee stops even if we exit early (failure/stop)
struct Finalizer {
std::atomic<bool>* ready;
@@ -2245,7 +2257,7 @@ void HeavyLanePool::EnsureMasterWarmup(const std::wstring& path, ImageID imageId
QuickView::RawImageFrame fullFrame;
{
- auto* ctx = new(std::nothrow) AuxLayerReadyCtx{ this, path, imageId };
+ auto* ctx = new(std::nothrow) AuxLayerReadyCtx{ this, path, imageId, targetSlot, generationId };
if (ctx) {
fullFrame.onAuxLayerReady.ctx = ctx;
fullFrame.onAuxLayerReady.pfn = [](void* c, std::unique_ptr<QuickView::AuxLayer> aux, QuickView::GpuBlendOp op, QuickView::GpuShaderPayload payload) {
@@ -2254,6 +2266,8 @@ void HeavyLanePool::EnsureMasterWarmup(const std::wstring& path, ImageID imageId
ev.type = EventType::AuxLayerReady;
ev.filePath = lctx->path;
ev.imageId = lctx->id;
+ ev.targetSlot = lctx->targetSlot;
+ ev.generationId = lctx->generationId;
ev.auxLayer = std::move(aux);
ev.blendOp = op;
ev.shaderPayload = payload;
@@ -3245,7 +3259,7 @@ HRESULT HeavyLanePool::FullDecodeAndCacheLOD(Worker& worker, const JobInfo& job,
const int targetW = (m_titanSrcW + (1 << lod) - 1) / (1 << lod);
const int targetH = (m_titanSrcH + (1 << lod) - 1) / (1 << lod);
{
- auto* ctx = new(std::nothrow) AuxLayerReadyCtx{ this, job.path, job.imageId };
+ auto* ctx = new(std::nothrow) AuxLayerReadyCtx{ this, job.path, job.imageId, job.targetSlot, job.generationId };
if (ctx) {
fullFrame.onAuxLayerReady.ctx = ctx;
fullFrame.onAuxLayerReady.pfn = [](void* c, std::unique_ptr<QuickView::AuxLayer> aux, QuickView::GpuBlendOp op, QuickView::GpuShaderPayload payload) {
@@ -3254,6 +3268,8 @@ HRESULT HeavyLanePool::FullDecodeAndCacheLOD(Worker& worker, const JobInfo& job,
ev.type = EventType::AuxLayerReady;
ev.filePath = lctx->path;
ev.imageId = lctx->id;
+ ev.targetSlot = lctx->targetSlot;
+ ev.generationId = lctx->generationId;
ev.auxLayer = std::move(aux);
ev.blendOp = op;
ev.shaderPayload = payload;
diff --git a/QuickView/HeavyLanePool.h b/QuickView/HeavyLanePool.h
index b2926c8..047ff13 100644
--- a/QuickView/HeavyLanePool.h
+++ b/QuickView/HeavyLanePool.h
@@ -1,5 +1,6 @@
#pragma once
#include "ImageEngine.h" // For EngineEvent complete type
+#include "PaneTypes.h"
#include "ImageLoader.h"
#include "MemoryArena.h"
#include "SystemInfo.h"
@@ -67,10 +68,10 @@ public:
// === Task Submission ===
// Thread-safe. Will auto-expand if needed.
// [ImageID] Uses stable path hash instead of incrementing token
- void Submit(const std::wstring& path, ImageID imageId, std::shared_ptr<QuickView::MappedFile> mmf = nullptr);
+ void Submit(const std::wstring& path, ImageID imageId, std::shared_ptr<QuickView::MappedFile> mmf = nullptr, PaneSlot targetSlot = PaneSlot::Primary, uint64_t generationId = 0);
// Full resolution decode (no scaling)
- void SubmitFullDecode(const std::wstring& path, ImageID imageId, std::shared_ptr<QuickView::MappedFile> mmf = nullptr);
+ void SubmitFullDecode(const std::wstring& path, ImageID imageId, std::shared_ptr<QuickView::MappedFile> mmf = nullptr, PaneSlot targetSlot = PaneSlot::Primary, uint64_t generationId = 0);
// [Titan Engine] Submit a tile decode task
void SubmitTile(const std::wstring& path, ImageID imageId, std::shared_ptr<QuickView::MappedFile> mmf, QuickView::TileCoord coord, QuickView::RegionRequest region, int priority = 0);
@@ -89,7 +90,7 @@ public:
// === Cancellation ===
// [ImageID] Cancel tasks that don't match the current imageId
- void CancelOthers(ImageID currentId);
+ void CancelOthers(ImageID currentId, PaneSlot targetSlot = PaneSlot::Primary);
void CancelAll();
// === Result Retrieval ===
@@ -276,6 +277,8 @@ private:
// Common
std::wstring path;
ImageID imageId;
+ PaneSlot targetSlot = PaneSlot::Primary;
+ uint64_t generationId = 0;
std::chrono::steady_clock::time_point submitTime; // [Metrics] Track queue time
std::shared_ptr<QuickView::MappedFile> mmf; // [Optimization] Zero-Copy MMF Source
float targetHdrHeadroomStops = -1.0f;
@@ -459,7 +462,7 @@ private:
std::atomic<ImageID> m_masterWarmupImageId{ 0 };
std::atomic<bool> m_masterWarmupReady{ false }; // [Direct-to-MMF] Set true when warmup decode is complete
bool ShouldWarmupMasterBacking() const;
- void EnsureMasterWarmup(const std::wstring& path, ImageID imageId, std::shared_ptr<QuickView::MappedFile> mmf);
+ void EnsureMasterWarmup(const std::wstring& path, ImageID imageId, std::shared_ptr<QuickView::MappedFile> mmf, PaneSlot targetSlot, uint64_t generationId);
void StopMasterWarmup();
// ============================================================================
diff --git a/QuickView/ImageEngine.cpp b/QuickView/ImageEngine.cpp
index 21d57e2..7a8d7cc 100644
--- a/QuickView/ImageEngine.cpp
+++ b/QuickView/ImageEngine.cpp
@@ -19,6 +19,8 @@ namespace {
ImageEngine* engine;
std::wstring path;
ImageID id;
+ PaneSlot targetSlot;
+ uint64_t generationId;
};
}
@@ -150,7 +152,7 @@ void ImageEngine::SetTargetHdrHeadroomStops(float stops) {
}
// Request full resolution decode for current image (used by JXL serial pipeline)
-void ImageEngine::RequestFullDecode(const std::wstring& path, ImageID imageId) {
+void ImageEngine::RequestFullDecode(const std::wstring& path, ImageID imageId, PaneSlot targetSlot, uint64_t generationId) {
// Node B: Decoding Complete / Request Full Decode
QV_LOG("ImageEngine_FullDecode",
TraceLoggingInt32(g_debugMetrics.lastUploadChannel.load(), "LastUploadChannel"),
@@ -161,7 +163,7 @@ void ImageEngine::RequestFullDecode(const std::wstring& path, ImageID imageId) {
if (!m_heavyPool) return;
// Only proceed if this is still the current image
- if (imageId != m_currentImageId.load()) {
+ if (imageId != m_currentImageIdBySlot[static_cast<int>(targetSlot)].load()) {
QV_LOG("Engine_FullDecode", TraceLoggingString("Cancelled ImageChanged", "Action"));
return;
}
@@ -178,7 +180,7 @@ void ImageEngine::RequestFullDecode(const std::wstring& path, ImageID imageId) {
// [Note] No MMF passed here because this is a delayed request (MMF might not persist unless Titan)
// Actually, if we are in Titan mode, m_mmf is valid. If not, it's null.
// It's safe to pass m_mmf (member) here.
- m_heavyPool->SubmitFullDecode(path, imageId, m_mmf);
+ m_heavyPool->SubmitFullDecode(path, imageId, m_mmf, targetSlot, generationId);
QV_LOG("Engine_FullDecode",
TraceLoggingString("Requested", "Action"),
@@ -186,7 +188,7 @@ void ImageEngine::RequestFullDecode(const std::wstring& path, ImageID imageId) {
}
// [Phase 2] Dispatcher Implementation
-void ImageEngine::DispatchImageLoad(const std::wstring& path, ImageID imageId, uintmax_t fileSize) {
+void ImageEngine::DispatchImageLoad(const std::wstring& path, ImageID imageId, uintmax_t fileSize, PaneSlot targetSlot, uint64_t generationId) {
// 1. Peek Header
CImageLoader::ImageHeaderInfo info = m_loader->PeekHeader(path.c_str());
@@ -343,6 +345,8 @@ void ImageEngine::DispatchImageLoad(const std::wstring& path, ImageID imageId, u
e.type = EventType::FullReady;
e.filePath = path;
e.imageId = imageId;
+ e.targetSlot = targetSlot;
+ e.generationId = generationId;
e.rawFrame = cachedFrame; // Zero-copy shared_ptr
// [Fix - Bug 7] Re-populate metadata from cache
@@ -413,7 +417,7 @@ void ImageEngine::DispatchImageLoad(const std::wstring& path, ImageID imageId, u
if (m_config.ForceRawDecode) {
// [Fix] Explicitly request Full Decode
// This ensures we bypass IDCT scaling and get the full sensor resolution
- m_heavyPool->SubmitFullDecode(path, imageId, primaryMMF);
+ m_heavyPool->SubmitFullDecode(path, imageId, primaryMMF, targetSlot, generationId);
return;
}
else if (info.hasEmbeddedThumb) {
@@ -455,7 +459,7 @@ void ImageEngine::DispatchImageLoad(const std::wstring& path, ImageID imageId, u
} else {
// [v7.2 Fix] Large WebP -> Force Heavy Direct (non-Titan is full decode).
QV_LOG("Dispatch_Route", TraceLoggingString("WebP Large HeavyDirect", "Action"));
- m_heavyPool->Submit(path, imageId, primaryMMF);
+ m_heavyPool->Submit(path, imageId, primaryMMF, targetSlot, generationId);
return;
}
}
@@ -470,7 +474,7 @@ void ImageEngine::DispatchImageLoad(const std::wstring& path, ImageID imageId, u
if (fmtLower == L"webp") {
QV_LOG("Dispatch_Route", TraceLoggingString("WebP Heavy HeavyDirect", "Action"));
- m_heavyPool->Submit(path, imageId, primaryMMF); // Base Layer Scaled
+ m_heavyPool->Submit(path, imageId, primaryMMF, targetSlot, generationId); // Base Layer Scaled
return;
}
@@ -481,6 +485,8 @@ void ImageEngine::DispatchImageLoad(const std::wstring& path, ImageID imageId, u
if (info.format == L"JXL") {
m_pendingJxlHeavyPath.clear();
m_pendingJxlHeavyId = 0;
+ m_pendingJxlHeavySlot = targetSlot;
+ m_pendingJxlHeavyGenerationId = generationId;
// Scene A: Small JXL (< 1MB AND < 2MP) -> FastLane Direct Full Decode
// 1MB = 1048576 bytes
@@ -493,7 +499,7 @@ void ImageEngine::DispatchImageLoad(const std::wstring& path, ImageID imageId, u
if (isSmall) {
QV_LOG("Dispatch_Route", TraceLoggingString("JXL Small FastLane", "Action"));
// FastLane will use target=0 if detected as small
- m_fastLane.Push(path, imageId, m_targetHdrHeadroomStops.load(std::memory_order_relaxed));
+ m_fastLane.Push(path, imageId, m_targetHdrHeadroomStops.load(std::memory_order_relaxed), targetSlot, generationId);
}
else {
if (enableTitan) {
@@ -504,13 +510,15 @@ void ImageEngine::DispatchImageLoad(const std::wstring& path, ImageID imageId, u
// [Fix] Stage 2 Trigger explicitly needs these to be set for the pending heavy decode
m_pendingJxlHeavyPath = path;
m_pendingJxlHeavyId = imageId;
+ m_pendingJxlHeavySlot = targetSlot;
+ m_pendingJxlHeavyGenerationId = generationId;
- m_heavyPool->Submit(path, imageId, primaryMMF);
+ m_heavyPool->Submit(path, imageId, primaryMMF, targetSlot, generationId);
} else {
// [v3.2.5 Restore] 鏅€氶潪Titan澶у瀷澶у浘锛屽儚鏃х増涓€鏍风洿鎺ヨ窇 FullDecode
// 鍏嶅幓 300ms 寤惰繜锛岄€熷害鏈€蹇紝骞跺ぉ鐒剁敱瑙g爜绔睍绀鸿嚜甯﹂瑙堝浘锛? QV_LOG("Dispatch_Route", TraceLoggingString("JXL Large HeavyFullDecode", "Action"));
- m_heavyPool->SubmitFullDecode(path, imageId, primaryMMF);
+ m_heavyPool->SubmitFullDecode(path, imageId, primaryMMF, targetSlot, generationId);
}
}
return; // JXL dispatched
@@ -559,10 +567,10 @@ void ImageEngine::DispatchImageLoad(const std::wstring& path, ImageID imageId, u
if (isSmall) {
QV_LOG("Dispatch_Route", TraceLoggingString("FormatSmall FastLane", "Action"));
- m_fastLane.Push(path, imageId, m_targetHdrHeadroomStops.load(std::memory_order_relaxed));
+ m_fastLane.Push(path, imageId, m_targetHdrHeadroomStops.load(std::memory_order_relaxed), targetSlot, generationId);
} else {
QV_LOG("Dispatch_Route", TraceLoggingString("FormatLarge HeavyLane", "Action"));
- m_heavyPool->Submit(path, imageId, primaryMMF);
+ m_heavyPool->Submit(path, imageId, primaryMMF, targetSlot, generationId);
}
return;
}
@@ -570,18 +578,18 @@ void ImageEngine::DispatchImageLoad(const std::wstring& path, ImageID imageId, u
// 7. Standard Routing
if (useHeavy) {
QV_LOG("Dispatch_Route", TraceLoggingString("HeavyLane", "Action"));
- m_heavyPool->Submit(path, imageId, primaryMMF);
+ m_heavyPool->Submit(path, imageId, primaryMMF, targetSlot, generationId);
}
if (useFastLane) {
// Avoid parallel duplicate work if Heavy is already taking it?
// Logic: TypeA -> FastLane only. TypeB -> Heavy only.
// Unknown type -> Parallel (Both).
QV_LOG("Dispatch_Route", TraceLoggingString("FastLane", "Action"));
- m_fastLane.Push(path, imageId, m_targetHdrHeadroomStops.load(std::memory_order_relaxed));
+ m_fastLane.Push(path, imageId, m_targetHdrHeadroomStops.load(std::memory_order_relaxed), targetSlot, generationId);
}
}
-void ImageEngine::NavigateTo(const std::wstring& path, uintmax_t fileSize, uint64_t navToken) {
+void ImageEngine::NavigateTo(const std::wstring& path, uintmax_t fileSize, uint64_t navToken, PaneSlot targetSlot, uint64_t generationId) {
if (path.empty()) return;
// [Phase 3] Store the navigation token
@@ -590,8 +598,9 @@ void ImageEngine::NavigateTo(const std::wstring& path, uintmax_t fileSize, uint6
// [ImageID Architecture] Compute stable hash
ImageID imageId = ComputePathHash(path);
m_currentImageId.store(imageId);
- // Cancel stale heavy/tile work immediately for all dispatch paths.
- m_heavyPool->CancelOthers(imageId);
+ m_currentImageIdBySlot[static_cast<int>(targetSlot)].store(imageId);
+ // Cancel stale heavy/tile work immediately for the target pane.
+ m_heavyPool->CancelOthers(imageId, targetSlot);
m_currentNavPath = path;
m_lastInputTime = std::chrono::steady_clock::now();
@@ -602,7 +611,11 @@ void ImageEngine::NavigateTo(const std::wstring& path, uintmax_t fileSize, uint6
m_baseLayerReady.store(false);
// Use Central Dispatcher
- DispatchImageLoad(path, imageId, fileSize);
+ DispatchImageLoad(path, imageId, fileSize, targetSlot, generationId);
+}
+
+ImageID ImageEngine::GetCurrentImageId(PaneSlot slot) const {
+ return m_currentImageIdBySlot[static_cast<int>(slot)].load();
}
bool ImageEngine::ShouldSkipFastLaneForFastFormat(const std::wstring& path) {
@@ -685,7 +698,7 @@ std::vector<EngineEvent> ImageEngine::PollState() {
// [JXL Serial] Trigger Stage 2 IMMEDIATELY for JXL (No 300ms wait)
if (m_pendingJxlHeavyId == e.imageId && m_pendingJxlHeavyId != 0) {
QV_LOG("PollState_Route", TraceLoggingString("JXL PreviewReady HeavyImmediate", "Action"));
- RequestFullDecode(m_pendingJxlHeavyPath, m_pendingJxlHeavyId);
+ RequestFullDecode(m_pendingJxlHeavyPath, m_pendingJxlHeavyId, m_pendingJxlHeavySlot, m_pendingJxlHeavyGenerationId);
m_stage2Requested = true;
m_pendingJxlHeavyId = 0;
}
@@ -699,7 +712,7 @@ std::vector<EngineEvent> ImageEngine::PollState() {
// [JXL Scene C] FastLane Aborted (Modular?) -> Trigger Heavy Immediately
if (m_pendingJxlHeavyId == e.imageId && m_pendingJxlHeavyId != 0) {
QV_LOG("PollState_Route", TraceLoggingString("FastLane Failed HeavyImmediate", "Action"));
- RequestFullDecode(m_pendingJxlHeavyPath, m_pendingJxlHeavyId);
+ RequestFullDecode(m_pendingJxlHeavyPath, m_pendingJxlHeavyId, m_pendingJxlHeavySlot, m_pendingJxlHeavyGenerationId);
m_stage2Requested = true; // Mark as requested
m_pendingJxlHeavyId = 0; // Consumed
}
@@ -770,11 +783,11 @@ std::vector<EngineEvent> ImageEngine::PollState() {
// Check Timer (300ms idle)
// Only if pending JXL is waiting and not already requested
- if (m_isViewingScaledImage && !m_stage2Requested && m_pendingJxlHeavyId != 0 && m_pendingJxlHeavyId == m_currentImageId.load()) {
+ if (m_isViewingScaledImage && !m_stage2Requested && m_pendingJxlHeavyId != 0 && m_pendingJxlHeavyId == m_currentImageIdBySlot[static_cast<int>(m_pendingJxlHeavySlot)].load()) {
auto now = std::chrono::steady_clock::now();
auto dur = std::chrono::duration_cast<std::chrono::milliseconds>(now - m_stage1Time).count();
if (dur > 300) {
- RequestFullDecode(m_currentNavPath, m_currentImageId.load());
+ RequestFullDecode(m_currentNavPath, m_currentImageIdBySlot[static_cast<int>(m_pendingJxlHeavySlot)].load(), m_pendingJxlHeavySlot, m_pendingJxlHeavyGenerationId);
m_stage2Requested = true;
m_pendingJxlHeavyId = 0; // Consumed
}
@@ -1057,11 +1070,11 @@ void ImageEngine::FastLane::Clear() {
// m_skipCount is not reset here, it accumulates for debug stats.
}
-void ImageEngine::FastLane::Push(const std::wstring& path, ImageID id, float targetHdrHeadroomStops) {
+void ImageEngine::FastLane::Push(const std::wstring& path, ImageID id, float targetHdrHeadroomStops, PaneSlot targetSlot, uint64_t generationId) {
if (m_stopSignal) return;
{
std::lock_guard lock(m_queueMutex);
- m_queue.push_back({path, id, targetHdrHeadroomStops});
+ m_queue.push_back({path, id, targetHdrHeadroomStops, targetSlot, generationId});
// [v3.1] Simplified Push: No complex anti-explosion here.
// UpdateView()'s "Ruthless Purge" handles queue depth.
}
@@ -1157,7 +1170,7 @@ void ImageEngine::FastLane::QueueWorker() {
// [Direct D2D] Load directly to RawImageFrame backed by Arena
{
- auto* ctx = new(std::nothrow) AuxLayerReadyCtx{ m_parent, cmd.path, cmd.id };
+ auto* ctx = new(std::nothrow) AuxLayerReadyCtx{ m_parent, cmd.path, cmd.id, cmd.targetSlot, cmd.generationId };
if (ctx) {
rawFrame.onAuxLayerReady.ctx = ctx;
rawFrame.onAuxLayerReady.pfn = [](void* c, std::unique_ptr<QuickView::AuxLayer> aux, QuickView::GpuBlendOp op, QuickView::GpuShaderPayload payload) {
@@ -1166,6 +1179,8 @@ void ImageEngine::FastLane::QueueWorker() {
ev.type = EventType::AuxLayerReady;
ev.filePath = lctx->path;
ev.imageId = lctx->id;
+ ev.targetSlot = lctx->targetSlot;
+ ev.generationId = lctx->generationId;
ev.auxLayer = std::move(aux);
ev.blendOp = op;
ev.shaderPayload = payload;
@@ -1195,7 +1210,9 @@ void ImageEngine::FastLane::QueueWorker() {
EngineEvent e;
e.type = isClear ? EventType::FullReady : EventType::PreviewReady;
e.filePath = cmd.path;
- e.imageId = cmd.id;
+ e.imageId = cmd.id;
+ e.targetSlot = cmd.targetSlot;
+ e.generationId = cmd.generationId;
// [v8.16 Fix] DEEP COPY pixels to heap BEFORE outputting event!
// When FastLane immediately starts next job, Arena memory is reused.
@@ -1307,6 +1324,8 @@ void ImageEngine::FastLane::QueueWorker() {
e.type = EventType::LoadError;
e.filePath = cmd.path;
e.imageId = cmd.id;
+ e.targetSlot = cmd.targetSlot;
+ e.generationId = cmd.generationId;
e.hr = hr; // Propagate error code
{
std::lock_guard lock(m_queueMutex);
@@ -1336,7 +1355,7 @@ void ImageEngine::SetPrefetchPolicy(const PrefetchPolicy& policy) {
void ImageEngine::TriggerPendingJxlHeavy() {
if (!m_pendingJxlHeavyPath.empty() && m_pendingJxlHeavyId != 0) {
QV_LOG("PollState_Route", TraceLoggingString("JXL Sequential TriggerHeavy", "Action"));
- m_heavyPool->Submit(m_pendingJxlHeavyPath, m_pendingJxlHeavyId);
+ m_heavyPool->Submit(m_pendingJxlHeavyPath, m_pendingJxlHeavyId, nullptr, m_pendingJxlHeavySlot, m_pendingJxlHeavyGenerationId);
m_pendingJxlHeavyPath.clear();
m_pendingJxlHeavyId = 0;
}
diff --git a/QuickView/ImageEngine.h b/QuickView/ImageEngine.h
index 8dea20d..923040f 100644
--- a/QuickView/ImageEngine.h
+++ b/QuickView/ImageEngine.h
@@ -20,6 +20,7 @@
#include "EditState.h"
#include "SystemInfo.h" // [N+1] Hardware detection & auto-config
#include "FileNavigator.h" // [ImageID] For ImageID type and ComputePathHash
+#include "PaneTypes.h"
// Forward declarations
class HeavyLanePool;
@@ -54,6 +55,8 @@ struct EngineEvent {
std::wstring filePath; // Which file is this event for?
uint64_t navToken = 0; // [Phase 3] Navigation session token (deprecated, use imageId)
ImageID imageId = 0; // [ImageID Architecture] Stable content-based hash
+ PaneSlot targetSlot = PaneSlot::Primary;
+ uint64_t generationId = 0;
// Payload (Optional parts)
ComPtr<IWICBitmapSource> fullImage; // For FullReady (Legacy, being deprecated)
@@ -96,7 +99,7 @@ public:
/// </summary>
// fileSize: Used for Threshold Dispatch (Fast/Heavy/Express)
// navToken: [Phase 3] Navigation session token for event filtering
- void NavigateTo(const std::wstring& path, uintmax_t fileSize = 0, uint64_t navToken = 0);
+ void NavigateTo(const std::wstring& path, uintmax_t fileSize = 0, uint64_t navToken = 0, PaneSlot targetSlot = PaneSlot::Primary, uint64_t generationId = 0);
void SetWindow(HWND hwnd);
void SetTargetHdrHeadroomStops(float stops);
@@ -104,7 +107,7 @@ public:
void CancelHeavy(); // Implementation in ImageEngine.cpp
// Request full resolution decode for current image (used by JXL serial pipeline)
- void RequestFullDecode(const std::wstring& path, ImageID imageId);
+ void RequestFullDecode(const std::wstring& path, ImageID imageId, PaneSlot targetSlot = PaneSlot::Primary, uint64_t generationId = 0);
// [JXL Sequential] Trigger pending Heavy task after FastLane completes
void TriggerPendingJxlHeavy();
@@ -137,6 +140,7 @@ public:
// [v4.0] Infrastructure: Global Token Access
ImageID GetGlobalToken() const { return m_currentImageId.load(); }
+ ImageID GetCurrentImageId(PaneSlot slot) const;
CImageLoader* GetLoader() const { return m_loader; }
// [Phase 6] Dynamic Gating
@@ -290,7 +294,7 @@ private:
bool ShouldSkipFastLaneForFastFormat(const std::wstring& path);
// [Phase 2] Dispatcher
- void DispatchImageLoad(const std::wstring& path, ImageID imageId, uintmax_t fileSize);
+ void DispatchImageLoad(const std::wstring& path, ImageID imageId, uintmax_t fileSize, PaneSlot targetSlot, uint64_t generationId);
// --- Lane 1: The Fast Lane ---
class FastLane {
@@ -302,7 +306,7 @@ private:
// [v3.1] Ruthless Purge: Clear pending queue
// [v3.1] Ruthless Purge: Clear pending queue
void Clear();
- void Push(const std::wstring& path, ImageID id, float targetHdrHeadroomStops = -1.0f);
+ void Push(const std::wstring& path, ImageID id, float targetHdrHeadroomStops = -1.0f, PaneSlot targetSlot = PaneSlot::Primary, uint64_t generationId = 0);
std::optional<EngineEvent> TryPopResult();
bool IsQueueEmpty() const;
@@ -341,6 +345,8 @@ private:
std::wstring path;
ImageID id;
float targetHdrHeadroomStops = -1.0f;
+ PaneSlot targetSlot = PaneSlot::Primary;
+ uint64_t generationId = 0;
};
std::deque<FastLaneCommand> m_queue;
std::deque<EngineEvent> m_results;
@@ -367,11 +373,14 @@ private:
// [ImageID Architecture] Stable content-based ID for current image
std::atomic<ImageID> m_currentImageId{0};
+ std::atomic<ImageID> m_currentImageIdBySlot[2]{};
std::atomic<bool> m_baseLayerReady{false};
// [JXL Sequential] Pending Heavy task - waits for FastLane completion
std::wstring m_pendingJxlHeavyPath;
ImageID m_pendingJxlHeavyId = 0;
+ PaneSlot m_pendingJxlHeavySlot = PaneSlot::Primary;
+ uint64_t m_pendingJxlHeavyGenerationId = 0;
// [JXL Serial] State Tracking
bool m_isViewingScaledImage = false; // True if current view is Fast/Preview (Scaled)
diff --git a/QuickView/ImageLoader.cpp b/QuickView/ImageLoader.cpp
index 45d822c..2245cbb 100644
--- a/QuickView/ImageLoader.cpp
+++ b/QuickView/ImageLoader.cpp
@@ -45,7 +45,7 @@ using namespace QuickView;
#include <shobjidl.h> // [Add] for IShellItemImageFactory
#include <thread>
-extern FileNavigator g_navigator;
+extern FileNavigator& g_navigator;
// Forward declaration
static bool ReadFileToVector(LPCWSTR filePath, std::vector<uint8_t> &buffer);
diff --git a/QuickView/ImageResource.h b/QuickView/ImageResource.h
new file mode 100644
index 0000000..64a6d5e
--- /dev/null
+++ b/QuickView/ImageResource.h
@@ -0,0 +1,107 @@
+/*
+ * QuickView - Image Resource Management and GPU Asset Handles
+ * Copyright (C) 2026-Present QuickView Contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "pch.h"
+#include "ImageTypes.h"
+#include "AnimationDecoder.h"
+
+#include <d2d1_2.h>
+#include <d2d1_3.h>
+#include <dxgi1_2.h>
+#include <memory>
+#include <wrl/client.h>
+
+using Microsoft::WRL::ComPtr;
+
+struct ImageResource {
+ ComPtr<ID2D1Bitmap> bitmap;
+ ComPtr<ID2D1SvgDocument> svgDoc;
+ bool isSvg = false;
+ float svgW = 0.0f;
+ float svgH = 0.0f;
+
+ QuickView::GpuBlendOp blendOp = QuickView::GpuBlendOp::None;
+ QuickView::GpuShaderPayload shaderPayload = {};
+ std::unique_ptr<QuickView::AuxLayer> auxLayer;
+
+ std::shared_ptr<QuickView::IAnimationDecoder> animator;
+ QuickView::AnimationFrameMeta frameMeta;
+
+ void Reset() {
+ bitmap.Reset();
+ svgDoc.Reset();
+ isSvg = false;
+ svgW = 0.0f;
+ svgH = 0.0f;
+ blendOp = QuickView::GpuBlendOp::None;
+ shaderPayload = {};
+ auxLayer.reset();
+ animator.reset();
+ frameMeta = {};
+ }
+
+ ImageResource() = default;
+ ImageResource(const ImageResource&) = delete;
+ ImageResource& operator=(const ImageResource&) = delete;
+ ImageResource(ImageResource&&) = default;
+ ImageResource& operator=(ImageResource&&) = default;
+
+ ImageResource Clone() const {
+ ImageResource cloned;
+ cloned.bitmap = bitmap;
+ cloned.svgDoc = svgDoc;
+ cloned.isSvg = isSvg;
+ cloned.svgW = svgW;
+ cloned.svgH = svgH;
+ cloned.blendOp = blendOp;
+ cloned.shaderPayload = shaderPayload;
+ if (auxLayer) {
+ cloned.auxLayer = auxLayer->Clone();
+ }
+ cloned.animator = animator;
+ cloned.frameMeta = frameMeta;
+ return cloned;
+ }
+
+ D2D1_SIZE_F GetSize() const {
+ if (isSvg) return D2D1::SizeF(svgW, svgH);
+ if (bitmap) return bitmap->GetSize();
+ return D2D1::SizeF(0.0f, 0.0f);
+ }
+
+ operator bool() const {
+ return (isSvg && svgDoc) || bitmap;
+ }
+};
+
+inline DXGI_FORMAT GetImageResourceSurfaceFormat(const ImageResource& resource) {
+ if (!resource.bitmap) {
+ return DXGI_FORMAT_B8G8R8A8_UNORM;
+ }
+
+ const DXGI_FORMAT bitmapFormat = resource.bitmap->GetPixelFormat().format;
+ if (bitmapFormat == DXGI_FORMAT_R16G16B16A16_FLOAT ||
+ bitmapFormat == DXGI_FORMAT_R32G32B32A32_FLOAT) {
+ return DXGI_FORMAT_R16G16B16A16_FLOAT;
+ }
+
+ return DXGI_FORMAT_B8G8R8A8_UNORM;
+}
+
diff --git a/QuickView/PaneContext.h b/QuickView/PaneContext.h
new file mode 100644
index 0000000..eba0644
--- /dev/null
+++ b/QuickView/PaneContext.h
@@ -0,0 +1,64 @@
+/*
+ * QuickView - Compare Mode Pane Context and State Tracking
+ * Copyright (C) 2026-Present QuickView Contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "PaneTypes.h"
+#include "ImageResource.h"
+#include "EditState.h"
+#include "FileNavigator.h"
+#include "ImageLoader.h"
+
+#include <cstdint>
+#include <string>
+
+struct PaneContext {
+ ImageResource resource;
+ std::wstring path;
+ EditState editState;
+ ViewState view;
+ FileNavigator navigator;
+ CImageLoader::ImageMetadata metadata;
+ bool valid = false;
+ uint64_t generationId = 0;
+
+ int CmsModeOverride = -1;
+ bool EnableSoftProofing = false;
+ std::wstring SoftProofProfilePath;
+
+ void Reset() {
+ resource.Reset();
+ path.clear();
+ editState.Reset();
+ view.Reset();
+ metadata = {};
+ valid = false;
+ CmsModeOverride = -1;
+ EnableSoftProofing = false;
+ SoftProofProfilePath.clear();
+ ++generationId;
+ }
+};
+
+extern PaneContext g_panes[2];
+
+inline PaneContext& GetPaneContext(PaneSlot slot) {
+ return g_panes[static_cast<int>(slot)];
+}
+
+
diff --git a/QuickView/PaneTypes.h b/QuickView/PaneTypes.h
new file mode 100644
index 0000000..b58077d
--- /dev/null
+++ b/QuickView/PaneTypes.h
@@ -0,0 +1,25 @@
+/*
+ * QuickView - Compare Mode Pane Slot Enumeration
+ * Copyright (C) 2026-Present QuickView Contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+enum class PaneSlot {
+ Primary = 0,
+ Left = 1
+};
+
diff --git a/QuickView/SettingsOverlay.cpp b/QuickView/SettingsOverlay.cpp
index c0dacc1..ad7d592 100644
--- a/QuickView/SettingsOverlay.cpp
+++ b/QuickView/SettingsOverlay.cpp
@@ -26,8 +26,8 @@
extern ImageEngine* g_pImageEngine;
extern AppConfig g_config;
extern RuntimeConfig g_runtime;
-extern std::wstring g_imagePath;
-extern FileNavigator g_navigator;
+extern std::wstring& g_imagePath;
+extern FileNavigator& g_navigator;
extern Toolbar g_toolbar; // [Fix] Allow Settings to update toolbar state directly
extern HelpOverlay g_helpOverlay;
diff --git a/QuickView/ThumbnailManager.cpp b/QuickView/ThumbnailManager.cpp
index 8e4d655..ca21d7a 100644
--- a/QuickView/ThumbnailManager.cpp
+++ b/QuickView/ThumbnailManager.cpp
@@ -3,7 +3,7 @@
#include <algorithm>
#include <cwctype>
#include "FileNavigator.h"
-extern FileNavigator g_navigator;
+extern FileNavigator& g_navigator;
ThumbnailManager::ThumbnailManager() {}
diff --git a/QuickView/Toolbar.cpp b/QuickView/Toolbar.cpp
index 0f451f6..6e090f0 100644
--- a/QuickView/Toolbar.cpp
+++ b/QuickView/Toolbar.cpp
@@ -8,7 +8,7 @@
using QuickView::UI::GeekIconRenderer;
extern AppConfig g_config;
-extern FileNavigator g_navigator;
+extern FileNavigator& g_navigator;
namespace Icons = GeekIcons;
diff --git a/QuickView/UIRenderer.cpp b/QuickView/UIRenderer.cpp
index 6026e19..dfd3eaf 100644
--- a/QuickView/UIRenderer.cpp
+++ b/QuickView/UIRenderer.cpp
@@ -26,7 +26,7 @@ extern HelpOverlay g_helpOverlay;
extern ImageEngine* g_pImageEngine; // [v3.1] Accessor (renamed from g_imageEngine)
#include "FileNavigator.h"
-extern FileNavigator g_navigator;
+extern FileNavigator& g_navigator;
// DrawDialog is still in main.cpp (modal dialog handling)
extern void DrawDialog(ID2D1DeviceContext* context, const RECT& clientRect);
@@ -35,9 +35,9 @@ extern RuntimeConfig g_runtime;
extern bool g_isLoading; // [Fix] Loading indicator for progress bar
extern bool g_isLeftPaneDecoding; // [Fix] Left pane decoding status
extern bool g_isNavigatingToTitan; // [Fix] Restrict decode progress bar to Titan images
-extern ViewState g_viewState; // [v3.2] For Nav Indicators
-extern CImageLoader::ImageMetadata g_currentMetadata; // [v3.2] For Info Panel
-extern std::wstring g_imagePath; // [v3.2] For Info Panel
+extern ViewState& g_viewState; // [v3.2] For Nav Indicators
+extern CImageLoader::ImageMetadata& g_currentMetadata; // [v3.2] For Info Panel
+extern std::wstring& g_imagePath; // [v3.2] For Info Panel
extern bool g_slowMotionMode; // [Debug] Slow-motion crossfade mode
extern AppConfig g_config;
extern int GetCurrentZoomPercent(); // [v3.2.3] For Info Panel Zoom Display
diff --git a/QuickView/main.cpp b/QuickView/main.cpp
index 29f30bb..ae73dea 100644
--- a/QuickView/main.cpp
+++ b/QuickView/main.cpp
@@ -16,6 +16,7 @@ static constexpr const char* CURRENT_MODULE = "Main";
#include "InputController.h" // Quantum Stream: Warp Mode
#include "LosslessTransform.h"
#include "EditState.h"
+#include "PaneContext.h"
#include "AppStrings.h"
#include "ContextMenu.h"
#include "SupportedExtensions.h"
@@ -236,85 +237,11 @@ FlushMenuThemesFn LoadFlushMenuThemes() {
}
}
-// [Step 3] Unified Resource Management
-struct ImageResource {
- ComPtr<ID2D1Bitmap> bitmap;
- ComPtr<ID2D1SvgDocument> svgDoc;
- bool isSvg = false;
- float svgW = 0, svgH = 0;
-
- // [GPU Pipeline] Multi-layer composition
- QuickView::GpuBlendOp blendOp = QuickView::GpuBlendOp::None;
- QuickView::GpuShaderPayload shaderPayload = {};
- std::unique_ptr<QuickView::AuxLayer> auxLayer;
-
- // [v10.5] Animation state
- std::shared_ptr<QuickView::IAnimationDecoder> animator;
- QuickView::AnimationFrameMeta frameMeta;
-
- void Reset() {
- bitmap.Reset();
- svgDoc.Reset();
- isSvg = false;
- svgW = 0; svgH = 0;
- blendOp = QuickView::GpuBlendOp::None;
- shaderPayload = {};
- auxLayer.reset();
- animator.reset();
- frameMeta = {};
- }
-
- ImageResource() = default;
- ImageResource(const ImageResource&) = delete;
- ImageResource& operator=(const ImageResource&) = delete;
- ImageResource(ImageResource&&) = default;
- ImageResource& operator=(ImageResource&&) = default;
-
- ImageResource Clone() const {
- ImageResource cloned;
- cloned.bitmap = bitmap;
- cloned.svgDoc = svgDoc;
- cloned.isSvg = isSvg;
- cloned.svgW = svgW;
- cloned.svgH = svgH;
- cloned.blendOp = blendOp;
- cloned.shaderPayload = shaderPayload;
- if (auxLayer) {
- cloned.auxLayer = auxLayer->Clone();
- }
- cloned.animator = animator;
- cloned.frameMeta = frameMeta;
- return cloned;
- }
-
- D2D1_SIZE_F GetSize() const {
- if (isSvg) return D2D1::SizeF(svgW, svgH);
- if (bitmap) return bitmap->GetSize();
- return D2D1::SizeF(0, 0);
- }
-
- // Truthy check
- operator bool() const {
- return (isSvg && svgDoc) || bitmap;
- }
-};
-
-static DXGI_FORMAT GetImageResourceSurfaceFormat(const ImageResource& resource) {
- if (!resource.bitmap) {
- return DXGI_FORMAT_B8G8R8A8_UNORM;
- }
-
- const DXGI_FORMAT bitmapFormat = resource.bitmap->GetPixelFormat().format;
- if (bitmapFormat == DXGI_FORMAT_R16G16B16A16_FLOAT ||
- bitmapFormat == DXGI_FORMAT_R32G32B32A32_FLOAT) {
- return DXGI_FORMAT_R16G16B16A16_FLOAT;
- }
-
- return DXGI_FORMAT_B8G8R8A8_UNORM;
-}
-
-static ImageResource g_imageResource;
-std::wstring g_imagePath; // Non-static for extern access from UIRenderer
+PaneContext g_panes[2];
+FileNavigator& g_navigator = g_panes[0].navigator;
+std::wstring& g_imagePath = g_panes[0].path;
+CImageLoader::ImageMetadata& g_currentMetadata = g_panes[0].metadata;
+ViewState& g_viewState = g_panes[0].view;
static bool g_isImageDirty = true; // Feature: Conditional Image Repaint (DComp Optimization)
static bool g_isBlurry = false; // For Motion Blur (Ghost)
static bool g_isCrossFading = false;
@@ -332,20 +259,16 @@ static const UINT_PTR TIMER_ID_REGISTRY_CHECK = 993;
DWORD g_toolbarHideTime = 0; // For auto-hide delay
static DialogState g_dialog;
-static EditState g_editState;
AppConfig g_config;
RuntimeConfig g_runtime;
-ViewState g_viewState; // Non-static for extern access from UIRenderer
bool g_preserveViewStateOnNextLoad = false;
ViewState g_preservedViewState;
static int g_renderExifOrientation = 1; // Exif orientation baked into the bitmap surface
-FileNavigator g_navigator; // New Navigator (Non-static for extern access from SettingsOverlay)
static ThumbnailManager g_thumbMgr;
GalleryOverlay g_gallery; // Non-static for extern access from UIRenderer
Toolbar g_toolbar; // Non-static for extern access from UIRenderer
SettingsOverlay g_settingsOverlay; // Non-static for extern access from UIRenderer
HelpOverlay g_helpOverlay; // Non-static for extern access