There were a few "ambiguous operator new/delete" errors that were due to
inheriting from a private base class that used the leak detector. These
errors are resolved by adding the leak detector to the derived classes.
JUCE_API was missing from a few useful types, notably the ARA hosting
types.
This fixes an issue where Direct2D will emit an error when using dirty
rects on the first full frame after resizing.
The issue isn't present on all hardware/drivers, but was observed on a
Windows 11 computer with a 890M iGPU.
Previously, IDXGISwapChain::Present was called on a background thread,
which made it difficult to avoid race conditions. e.g. during a
live-resize of a window, we would occasionally draw old incomplete
frames instead of new frames at the correct size.
The new approach moves the Present call to the main thread via
AsyncUpdater. We attempt to present whenever the swap event wakes, and
whenever a frame is drawn. Only a single Present call may be made after
the swap event wakes. Subsequent Present calls will be ignored until the
next time the swap event wakes.
Previously, this function didn't store/copy the provided bitmap, so the
resulting image was blank. This also broke createSnapshot(), which would
always return a blank image.
Problem description
===================
Firstly, the linked-list of pending presentations acted as a stack
(FILO). If the swap chain thread and main thread processed frames at
varying rates, then the following sequence of events was possible:
Main thread Swap chain thread Queue state
---------------------------------------------------------
Push frame (1) [1]
Push frame (2) [2, 1]
Pop frame (2) [1]
Push frame (3) [3, 1]
Pop frame (3) [1]
Pop frame (1) [] <-- Out of sequence!
Secondly, the swap chain's sequential flip model can only maintain a
valid back-buffer state as long as the list of dirty rects is correct,
and every pixel within the dirty rects is painted incrementally.
In the example above, if the main thread were to produce two frames
before the swap chain thread could present any frame, then presenting
*only* the frame 2 (skipping frame 1) may produce incorrect results when
combined with the existing back buffer. This is because regions updated
in frame 1 may not be updated in frame 2, so regions *only* updated in
frame 1 will be omitted from the back buffer.
Mitigation
==========
This patch removes the old stack of presentations and replaces it with a
slightly more complex mechanism that tracks two different Presentation
objects. At any time, up to one Presentation may be in use by the swap
chain thread (i.e. actively presenting), up to one Presentation may be
accumulating updated/dirty regions (i.e. painting), and up to one region
may be ready, awaiting display.
This scheme resolves the first issue described above by ensuring that
old frame data is not kept around. There is never more than one frame
awaiting display, which means that if the swap chain thread attempts to
display twice in a row (before the main thread produces a new frame),
the second attempt will be a no-op.
The second issue is resolved by accumulating changes into a single
Presentation whenever the main thread produces two or more frames in a
row. If there is already a 'ready' Presentation when the main thread
finishes painting, then all updated regions from the newest Presentation
will be added to the 'ready' Presentation, rather than replacing it.
When the swap chain thread is ready to present, it will therefore see
the result of all the accumulated Presentations produced by the main
thread, instead of just the newest Presentation.
Resizing using window manager functionality (e.g. clicking and dragging
in the non-client area) will send WM_SIZING to the window, which in turn
will enable continuous repainting in the D2D renderer until the resize
operation ends.
Continuous repainting is required in order for the window to display
correctly during the resize. Without continuous repainting, some frames
may not be completely painted, and may display with black areas,
producing a flickery effect.
When a resize is controlled entirely by the client, e.g. using the
corner resizer in the AudioPluginDemo standalone, WM_SIZING is never
posted. Instead, we assume that if the window has captured the cursor
during a setBounds call then it is probably resizing. We enable
continuous repainting in this case, and stop repainting once the window
releases the mouse.
An alternative appropach would be to add some kind of start/stop resize
API to ComponentPeer. I'm currently reluctant to do that because the
ComponentPeer API is already so large.