@@ -111,51 +111,92 @@ MailboxSwapChain::GetDesc(DXGI_SWAP_CHAIN_DESC* pDesc) {
111111}
112112
113113void MailboxSwapChain::ProducerCommit () {
114+ auto & ws = slots_[write_slot_];
115+ context4_->Signal (ws.fence .Get (), ++ws.fence_value );
116+
117+ // This runs one full render-cycle after the *previous* Signal was enqueued.
118+ // By then the D3D11 runtime has had ample opportunity to submit the prior
119+ // command buffer to the GPU, so GetCompletedValue() is far more likely to
120+ // have advanced than it would be inside ConsumerAcquire (which can be
121+ // called microseconds after the Signal). The check is non-blocking: if
122+ // the fence isn't done yet, we simply leave latest_completed_slot_ as-is
123+ // and try again next frame.
124+ //
125+ // On success we do a combined promotion CAS on mailbox_state_:
126+ // (has_pending=1, pending=P, completed=C, free=F)
127+ // → (has_pending=0, extra=C, completed=P, free=F)
128+ // then store latest_completed_slot_ = P with release ordering so that
129+ // ConsumerAcquire's acquire load cannot observe P before mailbox_state_
130+ // reflects P in the 'completed' role (i.e., protected from the producer).
114131 {
115- auto & ws = slots_[write_slot_];
116- context4_->Signal (ws.fence .Get (), ++ws.fence_value );
132+ uint32_t snap = mailbox_state_.load (std::memory_order_acquire);
133+ if (snap & (1u << 6 )) { // has_pending
134+ const int pend = static_cast <int >((snap >> 4 ) & 0x3u );
135+ const int comp = static_cast <int >((snap >> 2 ) & 0x3u );
136+ const int fr = static_cast <int >( snap & 0x3u );
137+ if (slots_[pend].fence ->GetCompletedValue () >=
138+ slots_[pend].fence_value ) {
139+ const uint32_t snap_desired =
140+ (static_cast <uint32_t >(comp) << 4 ) |
141+ (static_cast <uint32_t >(pend) << 2 ) |
142+ static_cast <uint32_t >(fr);
143+ if (mailbox_state_.compare_exchange_strong (
144+ snap, snap_desired,
145+ std::memory_order_acq_rel,
146+ std::memory_order_relaxed)) {
147+ latest_completed_slot_.store (pend, std::memory_order_release);
148+ }
149+ // CAS failure means no concurrent writer exists (ProducerCommit is
150+ // called from a single producer thread); the only way it can fail is
151+ // if mailbox_state_ was already has_pending=0, which means nothing
152+ // to promote. Either way, leave latest_completed_slot_ untouched.
153+ }
154+ }
117155 }
118156
119- const uint32_t desired = static_cast <uint32_t >(write_slot_) | 0x4u ;
157+ // Desired state:
158+ // has_pending = 1
159+ // pending = write_slot_ (new latest frame)
160+ // completed = old completed_slot (unchanged)
161+ // free = old pending_or_extra (recycled: was old pending or extra_free)
162+ //
163+ // new write_slot_ (producer-private) = old free_slot.
120164 uint32_t expected = mailbox_state_.load (std::memory_order_relaxed);
121- while (!mailbox_state_.compare_exchange_weak (
122- expected, desired, std::memory_order_release,
123- std::memory_order_relaxed)) {}
124- write_slot_ = static_cast <int >(expected & 0x3u );
165+ while (true ) {
166+ const int old_free = static_cast <int >( expected & 0x3u );
167+ const int old_completed = static_cast <int >((expected >> 2 ) & 0x3u );
168+ const int old_poe = static_cast <int >((expected >> 4 ) & 0x3u );
169+ const uint32_t desired =
170+ (1u << 6 ) |
171+ (static_cast <uint32_t >(write_slot_) << 4 ) |
172+ (static_cast <uint32_t >(old_completed) << 2 ) |
173+ static_cast <uint32_t >(old_poe);
174+ if (mailbox_state_.compare_exchange_weak (
175+ expected, desired,
176+ std::memory_order_release,
177+ std::memory_order_relaxed)) {
178+ write_slot_ = old_free;
179+ break ;
180+ }
181+ }
125182}
126183
127184HANDLE MailboxSwapChain::ConsumerAcquire () {
128- uint32_t expected = mailbox_state_.load (std::memory_order_acquire);
129- if (!(expected & 0x4u )) {
130- // No new frame — we already waited for this slot last time we acquired it.
131- return slots_[read_slot_].shared_handle ;
132- }
133- const uint32_t desired = static_cast <uint32_t >(read_slot_); // dirty=0
134- while (!mailbox_state_.compare_exchange_weak (
135- expected, desired, std::memory_order_acq_rel,
136- std::memory_order_relaxed)) {
137- if (!(expected & 0x4u ))
138- return slots_[read_slot_].shared_handle ;
139- }
140- read_slot_ = static_cast <int >(expected & 0x3u );
141-
142- auto & rs = slots_[read_slot_];
143- if (rs.fence ->GetCompletedValue () < rs.fence_value ) {
144- if (SUCCEEDED (rs.fence ->SetEventOnCompletion (rs.fence_value ,
145- rs.fence_event ))) {
146- ::WaitForSingleObject (rs.fence_event, INFINITE);
147- }
148- }
149- return rs.shared_handle ;
185+ // Always return the most recently fence-confirmed frame.
186+ // Advancement is handled exclusively by ProducerCommit (called one full
187+ // render-cycle after each Signal, where fence completion is far more
188+ // likely).
189+ return slots_[latest_completed_slot_.load (std::memory_order_acquire)]
190+ .shared_handle ;
150191}
151192
152193HRESULT MailboxSwapChain::Resize (int32_t width, int32_t height) {
153194 ReleaseSlots ();
154195 width_ = (width > 0 ) ? width : 1 ;
155196 height_ = (height > 0 ) ? height : 1 ;
156- mailbox_state_.store (2u , std::memory_order_relaxed);
197+ mailbox_state_.store (57u , std::memory_order_relaxed);
198+ latest_completed_slot_.store (2 , std::memory_order_relaxed);
157199 write_slot_ = 0 ;
158- read_slot_ = 1 ;
159200 return AllocateSlots ();
160201}
161202
@@ -184,7 +225,7 @@ HRESULT MailboxSwapChain::AllocateSlots() {
184225 desc.CPUAccessFlags = 0 ;
185226 desc.MiscFlags = D3D11_RESOURCE_MISC_SHARED;
186227
187- for (int i = 0 ; i < 3 ; ++i) {
228+ for (int i = 0 ; i < 4 ; ++i) {
188229 HRESULT hr = device_->CreateTexture2D (&desc, nullptr , &slots_[i].texture );
189230 if (FAILED (hr)) {
190231 std::cout << " media_kit: MailboxSwapChain: CreateTexture2D slot " << i
@@ -220,17 +261,6 @@ HRESULT MailboxSwapChain::AllocateSlots() {
220261 return hr;
221262 }
222263 slots_[i].fence_value = 0 ;
223-
224- slots_[i].fence_event =
225- ::CreateEventW (nullptr , /* bManualReset=*/ FALSE , /* bInitialState=*/ FALSE ,
226- nullptr );
227- if (!slots_[i].fence_event ) {
228- const HRESULT hrE = HRESULT_FROM_WIN32 (::GetLastError ());
229- std::cout << " media_kit: MailboxSwapChain: CreateEvent slot " << i
230- << " failed (hr=0x" << std::hex << hrE << std::dec << " )"
231- << std::endl;
232- return hrE;
233- }
234264 }
235265
236266 return S_OK;
@@ -242,9 +272,5 @@ void MailboxSwapChain::ReleaseSlots() {
242272 slot.shared_handle = nullptr ;
243273 slot.fence .Reset ();
244274 slot.fence_value = 0 ;
245- if (slot.fence_event ) {
246- ::CloseHandle (slot.fence_event);
247- slot.fence_event = nullptr ;
248- }
249275 }
250276}
0 commit comments