FYI, it fixed performance for Vampire: The Masquerade — Redemption (dx7 era game that has to rely on wined3d). Thanks a lot!

--
https://gitlab.winehq.org/wine/wine/-/merge_requests/9032#note_120381
As prescribed some time ago, this attempts to implement correct reparse
behaviour simply without attempting at all to make reparse points resolvable
Unix symlinks.
A reparse point consists two files. For a file whose NT name is "foobar", the
contents of the file or directory (and note that a reparse point *can* have
arbitrary contents) is stored in a file or directory "foobar?". The contents of
the reparse point are stored in a file "foobar*". Both * and ? are valid in
POSIX filenames but not NT, so no collision should be possible.
The actual file "foobar" does not exist in this case. This is to allow the case
where there are no reparse points (which is most common) to work as it currently
does, without any extra overhead to check for a reparse point. [Simply guarding
this behind FILE_OPEN_REPARSE_POINT does not seem good enough to me; that flag
is used for example by many builtin functions like MoveFile().]
When failing to locate a path segment "foobar", we check for "foobar*" and
attempt to parse and resolve that reparse point.
Issues:
(1) Since we are manipulating two files, are there any race conditions?
I believe there are no meaningful races.
Read/write races:
- When setting a reparse point, we move the contents file out of the way last,
so attempts to open the file before that will see a normal file. Attempts to
read the reparse point might see a partial file, but we treat that as
failure.
- When removing a reparse point from a file, we move the contents file back
first, so attempts to open or read will see a normal file.
- When deleting a reparse point entirely, we delete the contents first, so
attempts to open the file will see neither a normal file nor the contents of
the symlink, and fail. Attempts to query attributes don't need to open the
contents, only the data, which will either be present or not.
Write/write races:
- Races involving file deletion don't matter, because deletion doesn't take
effect until all handles to an inode are closed.
- If we try to simultaneously set the reparse point from two different
threads, one will fail with no effect since we create the reparse data file
with O_EXCL.
- If we try to remove the reparse point while setting it, the contents won't
be in the right place and we'll fail.
- If we try to remove the reparse point from two different threads, one will
fail to move the contents back or delete them.
(2) How does this interact with hard links?
Creating a hard link to an existing reparse point is not too hard to do; we
can just hard link both the data file and the contents file.
However, turning an existing hardlinked file into a reparse point cannot work.
This is also a limitation of other approaches that have been proposed.
For this reason I've elected to forbid hard link interaction for now.
One alternative approach that would allow turning hardlinked files into
reparse points would be to store the reparse data in xattr. This would be less
portable though.
--
v4: ntdll: Resolve IO_REPARSE_TAG_MOUNT_POINT during path lookup.
https://gitlab.winehq.org/wine/wine/-/merge_requests/9293
As prescribed some time ago, this attempts to implement correct reparse
behaviour simply without attempting at all to make reparse points resolvable
Unix symlinks.
A reparse point consists two files. For a file whose NT name is "foobar", the
contents of the file or directory (and note that a reparse point *can* have
arbitrary contents) is stored in a file or directory "foobar?". The contents of
the reparse point are stored in a file "foobar*". Both * and ? are valid in
POSIX filenames but not NT, so no collision should be possible.
The actual file "foobar" does not exist in this case. This is to allow the case
where there are no reparse points (which is most common) to work as it currently
does, without any extra overhead to check for a reparse point. [Simply guarding
this behind FILE_OPEN_REPARSE_POINT does not seem good enough to me; that flag
is used for example by many builtin functions like MoveFile().]
When failing to locate a path segment "foobar", we check for "foobar*" and
attempt to parse and resolve that reparse point.
Issues:
(1) Since we are manipulating two files, are there any race conditions?
I believe there are no meaningful races.
Read/write races:
- When setting a reparse point, we move the contents file out of the way last,
so attempts to open the file before that will see a normal file. Attempts to
read the reparse point might see a partial file, but we treat that as
failure.
- When removing a reparse point from a file, we move the contents file back
first, so attempts to open or read will see a normal file.
- When deleting a reparse point entirely, we delete the contents first, so
attempts to open the file will see neither a normal file nor the contents of
the symlink, and fail. Attempts to query attributes don't need to open the
contents, only the data, which will either be present or not.
Write/write races:
- Races involving file deletion don't matter, because deletion doesn't take
effect until all handles to an inode are closed.
- If we try to simultaneously set the reparse point from two different
threads, one will fail with no effect since we create the reparse data file
with O_EXCL.
- If we try to remove the reparse point while setting it, the contents won't
be in the right place and we'll fail.
- If we try to remove the reparse point from two different threads, one will
fail to move the contents back or delete them.
(2) How does this interact with hard links?
Creating a hard link to an existing reparse point is not too hard to do; we
can just hard link both the data file and the contents file.
However, turning an existing hardlinked file into a reparse point cannot work.
This is also a limitation of other approaches that have been proposed.
For this reason I've elected to forbid hard link interaction for now.
One alternative approach that would allow turning hardlinked files into
reparse points would be to store the reparse data in xattr. This would be less
portable though.
--
v2: ntdll: Pass attr and nt_name down to lookup_unix_name().
server: Implement FSCTL_GET_REPARSE_POINT.
server: Implement FSCTL_DELETE_REPARSE_POINT.
server: Implement FSCTL_SET_REPARSE_POINT.
ntdll/tests: Fix reparse test failures.
https://gitlab.winehq.org/wine/wine/-/merge_requests/9293
On Sat Nov 1 18:46:55 2025 +0000, QwertyChouskie wrote:
> > Good point, thanks. It does seem possible to use that extension, but I
> think we also need to support Nvidia. We could maybe have it as an
> alternative, though I’m not sure there are cases where it would actually
> help (that is, cases where the extension is available but a sufficient
> Vulkan driver isn’t).
> r600 (assuming it supports this extension) is certainly one case that
> comes to mind for something that is reasonably performant for older
> games but doesn't support Vulkan.
It also turned out that llvmpipe doesn’t support VK_EXT_map_memory_placed, and GL_AMD_pinned_memory would be a good fit for it. I plan to give that a try.
--
https://gitlab.winehq.org/wine/wine/-/merge_requests/9032#note_120365