Common/ObjectPool.c (C89) keeps objects of one class in chunks, the first
made at backend start: a freed object is pushed on a lock-free stack, an
allocation takes the stack whole (an exchange, so no ABA) and pushes the
rest back, or else takes the chunks' next slot. A pool destroyed with
objects still out is freed with the last of them. Classes take it with
OBJECT_POOL_MEMBERS / OBJECT_POOL_DEFINE: VKPipeline, VKShader,
VKTexture, VKFramebuffer and the vertex loaders, which are now their
own bucket list nodes. Vulkan's descriptor pool and cleanup lists get
their first room at boot.
Play allocations: 58 to 1 on the core's thread, 172 to 1 on the compile
workers (both a first use).
A Vulkan object destroyed once its command buffer is done is a record
of its kind, handle and heap allocation, in a list that keeps its
storage, where each was a std::function whose capture was allocated.
The descriptor pool lists and the command buffer arrays drop std::vector
and std::array too. Allocations after boot over the corpus fall from
2720 to 1702.
A staging texture says whether its upload memory stays mapped, and
StagingTransferDone tells without waiting whether the GPU is done with
its last copy, from the Vulkan fence counter, the Direct3D 12 fence
value or the GL sync object. Direct3D 12 upload heaps stay mapped
through their copies.
VKHeap.c takes large blocks from the driver and hands out offsets in
them: the lowest free span that fits, spans kept sorted so a free is a
binary search and a merge. The handle is a small struct kept by value,
so nothing is allocated per allocation. Host-visible blocks are mapped
once and stay mapped; stream buffers and staging buffers write straight
into the memory the GPU reads. Buffers and linear images keep to blocks
of their own, apart from optimal images. What the driver requires or
prefers dedicated, and anything of half a block or more, gets memory of
its own sized to fit. Host blocks are 16 MiB, device blocks 64 MiB, and
neither more than an eighth of its driver heap; one empty block of each
kind is kept and the rest go back to the driver.
The Vulkan Memory Allocator is gone, and with it the memory budget
extension only it used.
On Vulkan and Direct3D 12, in dual core, the GPU loop runs on a thread
of its own, as the frontend interface for both lets another thread
submit: retro_run starts the field, waits for it no longer than two of
the frontend's frames (40 ms at most), hands over the newest picture
finished and returns. A shader compiled, or anything else slow on the
GPU side, holds up the GPU thread only. The loop runs in slices of 5 ms,
so a request to park or quit is seen that soon; a slice that ends a
field counts it for retro_run.
Pictures go from the GPU thread to the frontend's through a queue: the
newest published, the one being handed over and the one shown, any of
which the frontend may read. Each output draws into images of its own,
three of them: a picture published and not yet handed over is drawn
into again in place. EndFrame hands over with set_image, or
set_texture_fenced and set_texture, on the frontend's thread, and an
image is free again once the frontend's frame that read it is
submitted. An XFB copy handed on in place is kept alive by the
presenter while the output holds it, and the output puts it back in the
layout or state the frontend reads it in before every submission of
the core's, as the frontend submits on its own thread whenever its
frame is due.
Savestates, cheats and disc changes let the field in flight end on the
GPU thread and park it while the frontend's thread has the video state;
unloading and a context destroyed stop it, and the next retro_run starts
it again.
Direct3D 11 and OpenGL keep the GPU loop on the frontend's thread: the
immediate context is shared with the frontend through lock_context,
which a thread holding it through a slow compile would hold the
frontend's frame up on, and the OpenGL context is the frontend's
thread's.
The frontend owns the window and presents, so the D3D11, D3D12 and
Vulkan backends draw their picture into a FrameOutput, a render target
the frontend reads where it is, rather than a swap chain. D3D11 hands
over one texture at pixel shader slot 0. D3D12 negotiates version 2 of
its interface and keeps one texture per sync index, handed over with
the fence its picture completes on, falling back to set_texture on
version 1. Vulkan keeps one image per sync index and hands it over with
set_image, so the fake swap chain and the WSI calls hooked under it are
gone, and the device may come without a surface. The outputs are RGBA8
and report that format to the presenter.
The DXGI and Vulkan swap chains, the backends' standalone device
creation and exclusive fullscreen are removed.
- CommandProcessor: the FIFO registers shared by the CPU and GPU
threads are retro_atomic words, read and written through LoadReg and
StoreReg; MMIO's LowPart/HighPart take those words.
- Fifo: the video buffer write and seen pointers, the sync ticks and
the stop request.
- PerfQueryBase: the query count and results sit behind base-class
helpers that every backend (Metal too) uses instead of touching the
atomics.
- Custom assets: the loader's memory delta and an asset's last load
time are 64-bit words; Present's swap flag is an int.
- D3D's input layout, Vulkan's waiting_for_submit.
- InputCommon: a NumericSetting's value and the Wiimote and Android
rumble levels are kept as their bits in a 64-bit word; the
ControllerInterface flags, counter and aspect ratio are int words.
- CachedBlob's fill position and stop flag.
UICommon/GameFileCache is deleted: only the Android app's game list
used it, and that is not part of the core.
Yellow squiggly lines begone!
Done automatically on .cpp files through `run-clang-tidy`, with manual corrections to the mistakes.
If an import is directly used, but is technically unnecessary since it's recursively imported by something else, it is *not* removed.
The tool doesn't touch .h files, so I did some of them by hand while fixing errors due to old recursive imports.
Not everything is removed, but the cleanup should be substantial enough.
Because this done on Linux, code that isn't used on it is mostly untouched.
(Hopefully no open PR is depending on these imports...)
SPDX standardizes how source code conveys its copyright and licensing
information. See https://spdx.github.io/spdx-spec/1-rationale/ . SPDX
tags are adopted in many large projects, including things like the Linux
kernel.
Since C++17, non-member std::size() is present in the standard library
which also operates on regular C arrays. Given that, we can just replace
usages of ArraySize with that where applicable.
In many cases, we can just change the actual C array ArraySize() was
called on into a std::array and just use its .size() member function
instead.
In some other cases, we can collapse the loops they were used in, into a
ranged-for loop, eliminating the need for en explicit bounds query.
Drivers can return VK_ERROR_OUT_OF_DATE_KHR from vkQueuePresentKHR, and
we should resize the image in this case, as well as when getting it back
from vkAcquireNextImageKHR.
Before this change, we simply fail if the device does not expose one
queue family that supports both graphics and present. Currently this is
fine, since devices tend to lay out their queues in this way. NV, for
instance, tends to have one queue family for all graphics operations and
one more for transfer only. However, it's not a hard requirement, and it
is cheap to use a separate queue, so we might as well.
Instead of resetting two command buffers, now we only have to call
vkResetCommandPool once at the start of a frame.
NV's recommends using one pool per frame/thread. May offer a very small
boost in performance on some systems.