Skip to content
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

IOS: Emulate FS timings for ES content IPC commands #9511

Merged
merged 6 commits into from Feb 19, 2021

Conversation

leoetlino
Copy link
Member

Filesystem accesses aren't magically faster when they are done by ES,
so this commit changes our content wrapper IPC commands to take FS
access times and read operations into account.

This should make content read timings a lot more accurate and closer
to console. Note that the accuracy of the timings are limited to the
accuracy of the emulated FS timings, and currently performance
differences between IOS9-IOS28 and newer IOS versions are not emulated.

Part 1 of fixing https://bugs.dolphin-emu.org/issues/11346
(part 2 will involve emulating those differences)

@iwubcode
Copy link
Contributor

@leoetlino - anything in particular to test or anything to watch out for when testing?

@JMC47
Copy link
Contributor

JMC47 commented Feb 14, 2021

The Wii System Menu is crashing when I try to access my Channel List in the Data Management screen. This does not happen in master.

Nevermind, it can happen on Master too, it depends on me accessing menus in a certain order.

@leoetlino leoetlino added QA done The PR has been checked to work properly. and removed needs testing labels Feb 14, 2021
@leoetlino
Copy link
Member Author

@iwubcode just making sure the system menu and NAND (including WiiWare) titles still work, nothing too specific

JMC said most games don't care about the timing changes, it's just VC games that take longer to load now.

Copy link
Member

@BhaaLseN BhaaLseN left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code seems good, nothing that sticks out - but untested.

@JMC47
Copy link
Contributor

JMC47 commented Feb 14, 2021

We solved the crash. Netplay corrupted my NAND at some point.

@leoetlino
Copy link
Member Author

yep, turned out there was a missing TMD for Dokapon Kingdom. It's why we enforce the requirement that games must be launched at least once before its save can be imported, but I guess this doesn't happen with netplay.

@leoetlino leoetlino marked this pull request as draft February 15, 2021 00:39
@leoetlino
Copy link
Member Author

After thinking about this a bit more, I'm not entirely satisfied with the way ticks are kept track of in the ES code -- I think that ideally ES shouldn't have to increment the number of ticks manually after each call to FS. I'll try to find a way to make that cleaner...

@leoetlino
Copy link
Member Author

leoetlino commented Feb 16, 2021

Still WIP, but I changed the way ticks were accumulated. For the new version of the third and fourth commits, I recommend reviewing with "Hide whitespace changes" enabled to make the diff less noisy.

I plan to address @Pokechu22's comment tomorrow.

Source/Core/Core/IOS/ES/TitleContents.cpp Outdated Show resolved Hide resolved
Source/Core/Core/IOS/ES/TitleContents.cpp Outdated Show resolved Hide resolved
@leoetlino leoetlino force-pushed the es-content-timings branch 2 times, most recently from 766f0c1 to fdd1fc3 Compare February 16, 2021 11:48
@leoetlino leoetlino marked this pull request as ready for review February 16, 2021 11:48
class Ticks
{
public:
Ticks(u64* ticks = nullptr) : m_ticks(ticks) {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

explicit ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case, we actually want implicit conversions to Ticks as this class is for all intents and purposes just a wrapper around a u64* and having to write Ticks{...} every time would be very verbose.

auto backend_fd = m_ios.GetFS()->OpenFile(request.uid, request.gid, request.path,
static_cast<Mode>(request.flags & 3));
LogResult(backend_fd, "OpenFile({})", request.path);
auto backend_fd = m_ios.GetFS()->OpenFile(uid, gid, path, mode);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ticks doesn't seem to be used. Did you mean to pass it into OpenFile here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I see this quite a bit. What is the point of updating the ticks when they are passed in as a copy?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The number of ticks is estimated and ticks is updated two lines above -- the underlying backend doesn't know anything about timings.

ticks isn't an integer, it's a small wrapper around a u64*, which is why it is passed by value.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @leoetlino for your explanation. I believe in my second comment I was confused ^^. In my first comment, I was aware of the u64* but thought the ticks were used in an underlying call somewhere.

Re-reading, I missed an important part. The ticks are ultimately passed in to the IPCReply structure via the MakeIPCReply function which I think helps determine the delay call for the command? Either the delay time is built up from 0 or it is built up from IPCOverheadTicks depending on the call.

Sorry for all the dumb comments!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's correct. MakeIPCReply is used to help construct an IPCReply with timing information and without having to duplicate the IPCReply{..., ticks} for every return.

Source/Core/Core/IOS/ES/TitleContents.cpp Outdated Show resolved Hide resolved
{
u64 title_id = 0;
if (!file.Read(&title_id, 1))
if (fs.Read(fd, &title_id, 1, ticks) != sizeof(title_id))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't a constant be used to indicate an invalid read or an invalid title_id rather than relying on sizeof?

Copy link
Member Author

@leoetlino leoetlino Feb 18, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sizeof(title_id) is that constant. I could hardcode 8 but I think that's worse than using sizeof.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, I just find odd that the Read function can have an error state and it's not being used to handle the error. I understand the reasoning behind this use of sizeof in the case of partially read. Maybe that's just me but I find it strange to read.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there is an error, Read() returns a negative value so the comparison will be false in that case. Or am I misunderstanding your comment?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was just saying that having if (Read(..., &var, ...) != READ_SIZE) rather than if (Read(..., &var, ...) < 0)/if (Read(..., &var, ...) == ERROR seems a bit odd to read to me.

Feel free to disregard that comment.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I see what you mean. But if we do if (Read(..., &var, ...) < 0) we still also need to check for partial reads, so we'd have to do something like

const int ret = Read(...);
if (ret < 0 || ret != READ_SIZE)
  ...

which seems redundant to me, since READ_SIZE is positive.

return {};

u32 uid = 0;
if (!file.Read(&uid, 1))
if (fs.Read(fd, &uid, 1, ticks) != sizeof(uid))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto for sizeof and uid.

Source/Core/Core/IOS/ES/Formats.cpp Show resolved Hide resolved
Source/Core/Core/IOS/ES/NandUtils.cpp Show resolved Hide resolved
Source/Core/Core/IOS/ES/ES.h Outdated Show resolved Hide resolved
The content ID/index is what actually matters..
We log FS reads already, might as well log ES content reads.
This makes it more convenient to emulate timings for IPC commands that
perform internal IOS <-> IOS IPC, for example ES relying on FS
for filesystem access.
Filesystem accesses aren't magically faster when they are done by ES,
so this commit changes our content wrapper IPC commands to take FS
access times and read operations into account.

This should make content read timings a lot more accurate and closer
to console. Note that the accuracy of the timings are limited to the
accuracy of the emulated FS timings, and currently performance
differences between IOS9-IOS28 and newer IOS versions are not emulated.

Part 1 of fixing https://bugs.dolphin-emu.org/issues/11346
(part 2 will involve emulating those differences)
@leoetlino leoetlino merged commit 93f9d67 into dolphin-emu:master Feb 19, 2021
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
QA done The PR has been checked to work properly.
6 participants