New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Read disc data asynchronously in a new thread #2149
Conversation
4f9c115
to
1c1fc21
Compare
It would be nice to have some sort of logging so we can compare how long a read takes in emulated time vs. real time it took. |
With something like DEBUG_LOG? Sure, I can do that. |
I've added logging (enable DVDInterface and Debug to see it), but the precision is apparently limited to 1 ms. Is std::chrono::steady_clock always this bad, or is it just a Windows problem? Are there any better options? |
b2dfc49
to
d5083f5
Compare
std::chrono::duration_caststd::chrono::microseconds ---> std::chrono::duration_caststd::chrono::nanoseconds perhaps? ref |
static std::thread g_DVD_thread; | ||
static std::mutex g_DVD_mutex; | ||
static std::condition_variable g_DVD_condition_working; | ||
static bool g_DVD_working = false; |
This comment was marked as off-topic.
This comment was marked as off-topic.
Sorry, something went wrong.
The result when changing microseconds to nanoseconds is pretty much the same, except 1000 times larger. |
e4ee5f8
to
75c8968
Compare
Common::Timer::GetTimeUs() has higher resolution. |
That works well. Thanks! |
I did test this a bit. I'm not sure if it was random, but when loading Metroid Prime (saved at the ship) I saw Samus already standing outside of the ship when it first loaded, then the cutscene played with her getting out of the ship. Maybe that can happen in master too, or maybe it can even happen on console. Otherwise, performance seemed fine. |
I've also been testing Metroid Prime a bit, and I've found an obvious issue that seems to be consistently reproducible. First, start the game (GM8E01) as usual, then press Start+B+X to restart the game. Dolphin will display the following two panic alerts:
After dismissing them, the game appears to be working normally at first, but there are several problems. When selecting a save file, the background music disappears. Once in-game, all audio is gone, and the HUD except for the health meter is also gone. The game also spawns you in the wrong room. My save was at the first save station in Phazon Mines, but I didn't start there. I started at the elevator going into Phazon Mines, with the elevator cutscene playing instead of the save station cutscene. When heading into the next room, the game acted as if it was my first time there, with the new area cutscene playing and the pirates present. My items and map were intact, though. The CopyToEmu that triggers the panic alert is indeed the one in DVDThread. That CopyToEmu is pretty much the only thing that this PR changes in the DI-CPU interface. Earlier, DVDInterface used GetPointer and then asked DiscIO to write to that pointer. DVDThread now has its own buffer that it asks DiscIO to write to, and then it uses CopyToEmu at the end. I assume that the GetPointer method skips some error checking and thus somehow works, but I really don't know much more than that. It's technically possible to change DVDThread's code back to GetPointer plus a memcpy, but from what I understand, that's bad to do... Does anyone know what to do with this? |
That's a bug in CopyToEmu: the range ending at 0x01800000 is valid (even though 0x01800000 itself can't be dereferenced). I guess in GetPointer(), |
That seems to do the trick. I've added a commit with that fix. |
ac301c2
to
fe318ce
Compare
CoreTiming::ScheduleEvent_Immediate(s_callback_event_type, DVDInterface::INT_TCINT); | ||
} | ||
|
||
void DVDThread() |
This comment was marked as off-topic.
This comment was marked as off-topic.
Sorry, something went wrong.
4635f82
to
5805dd7
Compare
GetPointer(address + u32(size)) != nullptr && | ||
size < EXRAM_SIZE); // Make sure we don't have a range spanning seperate 2 banks | ||
GetPointer(address + u32(size) - 1) != nullptr && | ||
size <= EXRAM_SIZE); // Make sure we don't have a range spanning 2 separate banks |
This comment was marked as off-topic.
This comment was marked as off-topic.
Sorry, something went wrong.
5805dd7
to
c040bc0
Compare
Common::Timer::GetTimeUs() - s_realtime_started_us, | ||
(CoreTiming::GetTicks() - s_time_read_started) / (SystemTimers::GetTicksPerSecond() / 1000 / 1000)); | ||
|
||
while (!s_DVD_success) |
This comment was marked as off-topic.
This comment was marked as off-topic.
Sorry, something went wrong.
This comment was marked as off-topic.
This comment was marked as off-topic.
Sorry, something went wrong.
I've checked out the "native" OS ways to do this on linux: There is the AIO framework which sounds like exactly what we want here, but with lots of pitfalls. eg the read must match the block size, ..., and the worst, those IOs bypass the cache. Still possible, but I'm not sure if this fits well. I have no clue about windows, but this sounds like just a generic implementation with a worker thread might be very fine... |
{ | ||
WaitUntilIdle(); | ||
|
||
DEBUG_LOG(DVDINTERFACE, "Disc has been read. Real time: %llu us. Real time including delay: %llu us. Emulated time including delay: %llu us.", |
This comment was marked as off-topic.
This comment was marked as off-topic.
Sorry, something went wrong.
I've tested this code quite a bit today, and it seems to work just great. I didn't expect dolphin to be blocked for 10ms often just for disk access, but we are. At least here on my hard disk. To be honest, I guess it may also be worth to delay it even more, eg as long as the real harddisk need. |
} | ||
|
||
if (s_DVD_success) | ||
Memory::CopyToEmu(s_output_address, s_DVD_buffer.data(), s_length); |
This comment was marked as off-topic.
This comment was marked as off-topic.
Sorry, something went wrong.
This comment was marked as off-topic.
This comment was marked as off-topic.
Sorry, something went wrong.
This comment was marked as off-topic.
This comment was marked as off-topic.
Sorry, something went wrong.
This comment was marked as off-topic.
This comment was marked as off-topic.
Sorry, something went wrong.
This comment was marked as off-topic.
This comment was marked as off-topic.
Sorry, something went wrong.
This comment was marked as off-topic.
This comment was marked as off-topic.
Sorry, something went wrong.
This comment was marked as off-topic.
This comment was marked as off-topic.
Sorry, something went wrong.
b649bf8
to
ec3ddb8
Compare
LGTM |
{ | ||
// Asking "Do you want to retry?" would be more user friendly, | ||
// but if panic alerts are turned off, the answer is automatically Yes, | ||
// and we don't want an infinite loop where ReadFromDisc always fails... |
This comment was marked as off-topic.
This comment was marked as off-topic.
Sorry, something went wrong.
This comment was marked as off-topic.
This comment was marked as off-topic.
Sorry, something went wrong.
This comment was marked as off-topic.
This comment was marked as off-topic.
Sorry, something went wrong.
This comment was marked as off-topic.
This comment was marked as off-topic.
Sorry, something went wrong.
This comment was marked as off-topic.
This comment was marked as off-topic.
Sorry, something went wrong.
This gives the CPU thread more time to do CPU things.
ValidCopyRange incorrectly returned false and stopped a CopyToEmu when pressing B+X+Start in some GameCube games (for instance Metroid Prime) after the DVD thread was implemented
It was previously an important part of DVDInterface, but since its usage there was replaced with DVDThread, the only remaining uses of it are in Boot and Boot_BS2Emu.
ec3ddb8
to
1202c2e
Compare
Read disc data asynchronously in a new thread
FifoCI detected that this change impacts graphical rendering. Here are the behavior differences detected by the system:
automated-fifoci-reporter |
DVDInterface inserts a delay when executing commands so that loading times will be similar to ones on consoles. During the delay, the CPU thread continues emulating the CPU. DVDInterface schedules a CoreTiming event so that it can execute the command after the delay. Executing the command involves reading data from the hard drive, a somewhat slow process... And it's happening on the CPU thread, so once the CoreTiming event starts running, CPU emulation needs to wait until DVDInterface is done.
This PR creates a new thread that reads data from the hard drive (and decompresses it, if a compressed disc image format is used) during the delay while the CPU thread is emulating the CPU. Copying the data to the emulated RAM is not done in this new thread; it's done in a CoreTiming event like before, so this PR shouldn't change the timing from the emulated system's point of view, and everything should still be deterministic. It could be useful to have an option to copy to RAM immediately after the data has been read, but I'm going to save that for a future PR.
@JMC47 Could you test the performance of this in a place where a lot of data is being read from the disc? FMVs, maybe? Make sure to have SUDTR off, because this isn't going to improve the performance of SUDTR. The difference in performance should be bigger if the ISO is on a slow storage device.