Skip to content

Commit 962590b

Browse files
committed
oscilloscope: try to roll our own implementation of left-scrolling to avoid Direct2D crashing windows
1 parent 585c96e commit 962590b

1 file changed

Lines changed: 56 additions & 8 deletions

File tree

src/dmt/gui/widget/Oscilloscope.h

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -329,16 +329,15 @@ class alignas(64) Oscilloscope : public juce::Thread
329329

330330
backImage = images[(size_t)currentFront].createCopy();
331331

332-
// Left scrolling 2.0: Hopefully without crashing host DAWs
332+
// Left scrolling 2.0: Thread-safe pixel copy to avoid Direct2D races on
333+
// Windows
333334
const int shift = jmin(pixelToDraw, width);
334335
if (shift > 0) {
335-
// Move content LEFT safely inside bounds
336-
// backImage.moveImageSection(0,
337-
// 0, // destX, destY
338-
// shift,
339-
// 0, // sourceX, sourceY
340-
// width - shift,
341-
// height);
336+
// Use BitmapData for guaranteed thread-safe, platform-agnostic pixel
337+
// operations. On Windows with Direct2D images, BitmapData access forces
338+
// software conversion, which is safe from worker threads. On macOS/Linux,
339+
// it accesses native format directly.
340+
scrollImageSectionLeft(backImage, shift, width, height);
342341

343342
backImage.clear(juce::Rectangle<int>(width - shift, 0, shift, height),
344343
juce::Colours::transparentBlack);
@@ -370,6 +369,55 @@ class alignas(64) Oscilloscope : public juce::Thread
370369

371370
//==============================================================================
372371
private:
372+
//==============================================================================
373+
/**
374+
* @brief Thread-safe pixel scroll operation for worker threads.
375+
*
376+
* @details
377+
* Manually copies pixels left by shiftAmount using BitmapData,
378+
* avoiding Direct2D's moveImageSection() which is not thread-safe
379+
* when called from worker threads on Windows.
380+
*
381+
* On Direct2D-backed images, accessing BitmapData forces a software
382+
* conversion, making this operation safe from any thread.
383+
* On software images, this is just a fast memcpy.
384+
*/
385+
static void scrollImageSectionLeft(Image& image,
386+
int shiftAmount,
387+
int width,
388+
int height) noexcept
389+
{
390+
if (shiftAmount <= 0 || width <= 0 || height <= 0)
391+
return;
392+
393+
const int srcX = shiftAmount;
394+
const int copyWidth = width - shiftAmount;
395+
396+
try {
397+
// Read-only access from source region
398+
const Image::BitmapData srcData(
399+
image, srcX, 0, copyWidth, height, Image::BitmapData::readOnly);
400+
// Write access to destination region
401+
const Image::BitmapData dstData(
402+
image, 0, 0, copyWidth, height, Image::BitmapData::readWrite);
403+
404+
// Verify format consistency
405+
if (srcData.pixelFormat != dstData.pixelFormat ||
406+
srcData.pixelStride != dstData.pixelStride)
407+
return;
408+
409+
// Copy each scanline
410+
const size_t lineSize = (size_t)dstData.pixelStride * (size_t)copyWidth;
411+
for (int y = 0; y < height; ++y) {
412+
std::memcpy(
413+
dstData.getLinePointer(y), srcData.getLinePointer(y), lineSize);
414+
}
415+
} catch (const std::exception&) {
416+
// BitmapData access failed; gracefully skip scroll
417+
// (better than crashing from Direct2D race)
418+
}
419+
}
420+
373421
//==============================================================================
374422
// Members initialized in the initializer list
375423
RingBuffer& ringBuffer;

0 commit comments

Comments
 (0)