Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
stenzek
GitHub Repository: stenzek/duckstation
Path: blob/master/src/core/cdrom_async_reader.cpp
4212 views
1
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <[email protected]>
2
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
3
4
#include "cdrom_async_reader.h"
5
#include "common/assert.h"
6
#include "common/log.h"
7
#include "common/timer.h"
8
LOG_CHANNEL(CDROMAsyncReader);
9
10
CDROMAsyncReader::CDROMAsyncReader() = default;
11
12
CDROMAsyncReader::~CDROMAsyncReader()
13
{
14
StopThread();
15
}
16
17
void CDROMAsyncReader::StartThread(u32 readahead_count)
18
{
19
if (IsUsingThread())
20
StopThread();
21
22
m_buffers.clear();
23
m_buffers.resize(readahead_count);
24
EmptyBuffers();
25
26
m_shutdown_flag.store(false);
27
m_read_thread = std::thread(&CDROMAsyncReader::WorkerThreadEntryPoint, this);
28
INFO_LOG("Read thread started with readahead of {} sectors", readahead_count);
29
}
30
31
void CDROMAsyncReader::StopThread()
32
{
33
if (!IsUsingThread())
34
return;
35
36
{
37
std::unique_lock lock(m_mutex);
38
m_shutdown_flag.store(true);
39
m_do_read_cv.notify_one();
40
}
41
42
m_read_thread.join();
43
EmptyBuffers();
44
m_buffers.clear();
45
}
46
47
void CDROMAsyncReader::SetMedia(std::unique_ptr<CDImage> media)
48
{
49
if (IsUsingThread())
50
CancelReadahead();
51
52
m_media = std::move(media);
53
}
54
55
std::unique_ptr<CDImage> CDROMAsyncReader::RemoveMedia()
56
{
57
if (IsUsingThread())
58
CancelReadahead();
59
60
return std::move(m_media);
61
}
62
63
bool CDROMAsyncReader::Precache(ProgressCallback* callback)
64
{
65
WaitForIdle();
66
67
std::unique_lock lock(m_mutex);
68
if (!m_media)
69
return false;
70
else if (m_media->IsPrecached())
71
return true;
72
73
const CDImage::PrecacheResult res = m_media->Precache(callback);
74
if (res == CDImage::PrecacheResult::Unsupported)
75
{
76
// fall back to copy precaching
77
std::unique_ptr<CDImage> memory_image = CDImage::CreateMemoryImage(m_media.get(), callback);
78
if (memory_image)
79
{
80
const CDImage::LBA lba = m_media->GetPositionOnDisc();
81
if (!memory_image->Seek(lba)) [[unlikely]]
82
{
83
ERROR_LOG("Failed to seek to LBA {} in memory image", lba);
84
return false;
85
}
86
87
m_media.reset();
88
m_media = std::move(memory_image);
89
return true;
90
}
91
else
92
{
93
return false;
94
}
95
}
96
97
return (res == CDImage::PrecacheResult::Success);
98
}
99
100
void CDROMAsyncReader::QueueReadSector(CDImage::LBA lba)
101
{
102
if (!IsUsingThread())
103
{
104
ReadSectorNonThreaded(lba);
105
return;
106
}
107
108
const u32 buffer_count = m_buffer_count.load();
109
if (buffer_count > 0)
110
{
111
// don't re-read the same sector if it was the last one we read
112
// the CDC code does this when seeking->reading
113
const u32 buffer_front = m_buffer_front.load();
114
if (m_buffers[buffer_front].lba == lba)
115
{
116
DEBUG_LOG("Skipping re-reading same sector {}", lba);
117
return;
118
}
119
120
// did we readahead to the correct sector?
121
const u32 next_buffer = (buffer_front + 1) % static_cast<u32>(m_buffers.size());
122
if (m_buffer_count > 1 && m_buffers[next_buffer].lba == lba)
123
{
124
// great, don't need a seek, but still kick the thread to start reading ahead again
125
DEBUG_LOG("Readahead buffer hit for sector {}", lba);
126
m_buffer_front.store(next_buffer);
127
m_buffer_count.fetch_sub(1);
128
m_can_readahead.store(true);
129
m_do_read_cv.notify_one();
130
return;
131
}
132
}
133
134
// we need to toss away our readahead and start fresh
135
DEBUG_LOG("Readahead buffer miss, queueing seek to {}", lba);
136
std::unique_lock lock(m_mutex);
137
m_next_position_set.store(true);
138
m_next_position = lba;
139
m_do_read_cv.notify_one();
140
}
141
142
bool CDROMAsyncReader::ReadSectorUncached(CDImage::LBA lba, CDImage::SubChannelQ* subq, SectorBuffer* data)
143
{
144
if (!IsUsingThread())
145
return InternalReadSectorUncached(lba, subq, data);
146
147
std::unique_lock lock(m_mutex);
148
149
// wait until the read thread is idle
150
m_notify_read_complete_cv.wait(lock, [this]() { return !m_is_reading.load(); });
151
152
// read while the lock is held so it has to wait
153
const CDImage::LBA prev_lba = m_media->GetPositionOnDisc();
154
const bool result = InternalReadSectorUncached(lba, subq, data);
155
if (!m_media->Seek(prev_lba)) [[unlikely]]
156
{
157
ERROR_LOG("Failed to re-seek to cached position {}", prev_lba);
158
m_can_readahead.store(false);
159
}
160
161
return result;
162
}
163
164
bool CDROMAsyncReader::InternalReadSectorUncached(CDImage::LBA lba, CDImage::SubChannelQ* subq, SectorBuffer* data)
165
{
166
if (m_media->GetPositionOnDisc() != lba && !m_media->Seek(lba)) [[unlikely]]
167
{
168
WARNING_LOG("Seek to LBA {} failed", lba);
169
return false;
170
}
171
172
if (!m_media->ReadRawSector(data, subq)) [[unlikely]]
173
{
174
WARNING_LOG("Read of LBA {} failed", lba);
175
return false;
176
}
177
178
return true;
179
}
180
181
bool CDROMAsyncReader::WaitForReadToComplete()
182
{
183
// Safe without locking with memory_order_seq_cst.
184
if (!m_next_position_set.load() && m_buffer_count.load() > 0)
185
{
186
TRACE_LOG("Returning sector {}", m_buffers[m_buffer_front.load()].lba);
187
return m_buffers[m_buffer_front.load()].result;
188
}
189
190
Timer wait_timer;
191
DEBUG_LOG("Sector read pending, waiting");
192
193
std::unique_lock lock(m_mutex);
194
m_notify_read_complete_cv.wait(
195
lock, [this]() { return (m_buffer_count.load() > 0 || m_seek_error.load()) && !m_next_position_set.load(); });
196
if (m_seek_error.load()) [[unlikely]]
197
{
198
m_seek_error.store(false);
199
return false;
200
}
201
202
const u32 front = m_buffer_front.load();
203
const double wait_time = wait_timer.GetTimeMilliseconds();
204
if (wait_time > 1.0f) [[unlikely]]
205
WARNING_LOG("Had to wait {:.2f} msec for LBA {}", wait_time, m_buffers[front].lba);
206
207
TRACE_LOG("Returning sector {} after waiting", m_buffers[front].lba);
208
return m_buffers[front].result;
209
}
210
211
void CDROMAsyncReader::WaitForIdle()
212
{
213
if (!IsUsingThread())
214
return;
215
216
std::unique_lock lock(m_mutex);
217
m_notify_read_complete_cv.wait(lock, [this]() { return (!m_is_reading.load() && !m_next_position_set.load()); });
218
}
219
220
void CDROMAsyncReader::EmptyBuffers()
221
{
222
m_buffer_front.store(0);
223
m_buffer_back.store(0);
224
m_buffer_count.store(0);
225
}
226
227
bool CDROMAsyncReader::ReadSectorIntoBuffer(std::unique_lock<std::mutex>& lock)
228
{
229
Timer timer;
230
231
const u32 slot = m_buffer_back.load();
232
m_buffer_back.store((slot + 1) % static_cast<u32>(m_buffers.size()));
233
234
BufferSlot& buffer = m_buffers[slot];
235
buffer.lba = m_media->GetPositionOnDisc();
236
m_is_reading.store(true);
237
lock.unlock();
238
239
TRACE_LOG("Reading LBA {}...", buffer.lba);
240
241
buffer.result = m_media->ReadRawSector(buffer.data.data(), &buffer.subq);
242
if (buffer.result) [[likely]]
243
{
244
const double read_time = timer.GetTimeMilliseconds();
245
if (read_time > 1.0f) [[unlikely]]
246
DEV_LOG("Read LBA {} took {:.2f} msec", buffer.lba, read_time);
247
}
248
else
249
{
250
ERROR_LOG("Read of LBA {} failed", buffer.lba);
251
}
252
253
lock.lock();
254
m_is_reading.store(false);
255
m_buffer_count.fetch_add(1);
256
m_notify_read_complete_cv.notify_all();
257
return true;
258
}
259
260
void CDROMAsyncReader::ReadSectorNonThreaded(CDImage::LBA lba)
261
{
262
Timer timer;
263
264
m_buffers.resize(1);
265
m_seek_error.store(false);
266
EmptyBuffers();
267
268
if (m_media->GetPositionOnDisc() != lba && !m_media->Seek(lba))
269
{
270
WARNING_LOG("Seek to LBA {} failed", lba);
271
m_seek_error.store(true);
272
return;
273
}
274
275
BufferSlot& buffer = m_buffers.front();
276
buffer.lba = m_media->GetPositionOnDisc();
277
278
TRACE_LOG("Reading LBA {}...", buffer.lba);
279
280
buffer.result = m_media->ReadRawSector(buffer.data.data(), &buffer.subq);
281
if (buffer.result) [[likely]]
282
{
283
const double read_time = timer.GetTimeMilliseconds();
284
if (read_time > 1.0f) [[unlikely]]
285
DEV_LOG("Read LBA {} took {:.2f} msec", buffer.lba, read_time);
286
}
287
else
288
{
289
ERROR_LOG("Read of LBA {} failed", buffer.lba);
290
}
291
292
m_buffer_count.fetch_add(1);
293
}
294
295
void CDROMAsyncReader::CancelReadahead()
296
{
297
DEV_LOG("Cancelling readahead");
298
299
std::unique_lock lock(m_mutex);
300
301
// wait until the read thread is idle
302
m_notify_read_complete_cv.wait(lock, [this]() { return !m_is_reading.load(); });
303
304
// prevent it from doing any more when it re-acquires the lock
305
m_can_readahead.store(false);
306
EmptyBuffers();
307
}
308
309
void CDROMAsyncReader::WorkerThreadEntryPoint()
310
{
311
std::unique_lock lock(m_mutex);
312
313
for (;;)
314
{
315
m_do_read_cv.wait(
316
lock, [this]() { return (m_shutdown_flag.load() || m_next_position_set.load() || m_can_readahead.load()); });
317
if (m_shutdown_flag.load())
318
break;
319
320
for (;;)
321
{
322
if (m_next_position_set.load())
323
{
324
// discard buffers, we're seeking to a new location
325
const CDImage::LBA seek_location = m_next_position.load();
326
EmptyBuffers();
327
m_next_position_set.store(false);
328
m_seek_error.store(false);
329
m_is_reading.store(true);
330
lock.unlock();
331
332
// seek without lock held in case it takes time
333
DEBUG_LOG("Seeking to LBA {}...", seek_location);
334
const bool seek_result = (m_media->GetPositionOnDisc() == seek_location || m_media->Seek(seek_location));
335
336
lock.lock();
337
m_is_reading.store(false);
338
339
// did another request come in? abort if so
340
if (m_next_position_set.load())
341
continue;
342
343
// did we fail the seek?
344
if (!seek_result) [[unlikely]]
345
{
346
// add the error result, and don't try to read ahead
347
WARNING_LOG("Seek to LBA {} failed", seek_location);
348
m_seek_error.store(true);
349
m_notify_read_complete_cv.notify_all();
350
break;
351
}
352
353
// go go read ahead!
354
m_can_readahead.store(true);
355
}
356
357
if (!m_can_readahead.load())
358
break;
359
360
// readahead time! read as many sectors as we have space for
361
DEBUG_LOG("Reading ahead {} sectors...", static_cast<u32>(m_buffers.size()) - m_buffer_count.load());
362
while (m_buffer_count.load() < static_cast<u32>(m_buffers.size()))
363
{
364
if (m_next_position_set.load())
365
{
366
// a seek request came in while we're reading, so bail out
367
break;
368
}
369
370
// stop reading if we hit the end or get an error
371
if (!ReadSectorIntoBuffer(lock))
372
break;
373
}
374
375
// readahead buffer is full or errored at this point
376
m_can_readahead.store(false);
377
break;
378
}
379
}
380
}
381
382