From 962590bec884de4dbd13edf58bafbeda2cfd9479 Mon Sep 17 00:00:00 2001 From: Lunix-420 Date: Sat, 9 May 2026 14:41:04 +0200 Subject: [PATCH 1/2] oscilloscope: try to roll our own implementation of left-scrolling to avoid Direct2D crashing windows --- src/dmt/gui/widget/Oscilloscope.h | 64 +++++++++++++++++++++++++++---- 1 file changed, 56 insertions(+), 8 deletions(-) diff --git a/src/dmt/gui/widget/Oscilloscope.h b/src/dmt/gui/widget/Oscilloscope.h index 5469b2c..0436b35 100644 --- a/src/dmt/gui/widget/Oscilloscope.h +++ b/src/dmt/gui/widget/Oscilloscope.h @@ -329,16 +329,15 @@ class alignas(64) Oscilloscope : public juce::Thread backImage = images[(size_t)currentFront].createCopy(); - // Left scrolling 2.0: Hopefully without crashing host DAWs + // Left scrolling 2.0: Thread-safe pixel copy to avoid Direct2D races on + // Windows const int shift = jmin(pixelToDraw, width); if (shift > 0) { - // Move content LEFT safely inside bounds - // backImage.moveImageSection(0, - // 0, // destX, destY - // shift, - // 0, // sourceX, sourceY - // width - shift, - // height); + // Use BitmapData for guaranteed thread-safe, platform-agnostic pixel + // operations. On Windows with Direct2D images, BitmapData access forces + // software conversion, which is safe from worker threads. On macOS/Linux, + // it accesses native format directly. + scrollImageSectionLeft(backImage, shift, width, height); backImage.clear(juce::Rectangle(width - shift, 0, shift, height), juce::Colours::transparentBlack); @@ -370,6 +369,55 @@ class alignas(64) Oscilloscope : public juce::Thread //============================================================================== private: + //============================================================================== + /** + * @brief Thread-safe pixel scroll operation for worker threads. + * + * @details + * Manually copies pixels left by shiftAmount using BitmapData, + * avoiding Direct2D's moveImageSection() which is not thread-safe + * when called from worker threads on Windows. + * + * On Direct2D-backed images, accessing BitmapData forces a software + * conversion, making this operation safe from any thread. + * On software images, this is just a fast memcpy. + */ + static void scrollImageSectionLeft(Image& image, + int shiftAmount, + int width, + int height) noexcept + { + if (shiftAmount <= 0 || width <= 0 || height <= 0) + return; + + const int srcX = shiftAmount; + const int copyWidth = width - shiftAmount; + + try { + // Read-only access from source region + const Image::BitmapData srcData( + image, srcX, 0, copyWidth, height, Image::BitmapData::readOnly); + // Write access to destination region + const Image::BitmapData dstData( + image, 0, 0, copyWidth, height, Image::BitmapData::readWrite); + + // Verify format consistency + if (srcData.pixelFormat != dstData.pixelFormat || + srcData.pixelStride != dstData.pixelStride) + return; + + // Copy each scanline + const size_t lineSize = (size_t)dstData.pixelStride * (size_t)copyWidth; + for (int y = 0; y < height; ++y) { + std::memcpy( + dstData.getLinePointer(y), srcData.getLinePointer(y), lineSize); + } + } catch (const std::exception&) { + // BitmapData access failed; gracefully skip scroll + // (better than crashing from Direct2D race) + } + } + //============================================================================== // Members initialized in the initializer list RingBuffer& ringBuffer; From 2f9089c0955233782ebb4ec9fb9613c48d2e946f Mon Sep 17 00:00:00 2001 From: Lunix-420 Date: Sat, 9 May 2026 15:30:56 +0200 Subject: [PATCH 2/2] oscilloscope: improve left scrolling implementation --- src/dmt/gui/widget/Oscilloscope.h | 35 +++++++++++++++---------------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/src/dmt/gui/widget/Oscilloscope.h b/src/dmt/gui/widget/Oscilloscope.h index 0436b35..ece9450 100644 --- a/src/dmt/gui/widget/Oscilloscope.h +++ b/src/dmt/gui/widget/Oscilloscope.h @@ -329,16 +329,16 @@ class alignas(64) Oscilloscope : public juce::Thread backImage = images[(size_t)currentFront].createCopy(); - // Left scrolling 2.0: Thread-safe pixel copy to avoid Direct2D races on - // Windows + // Left scrolling const int shift = jmin(pixelToDraw, width); if (shift > 0) { - // Use BitmapData for guaranteed thread-safe, platform-agnostic pixel - // operations. On Windows with Direct2D images, BitmapData access forces - // software conversion, which is safe from worker threads. On macOS/Linux, - // it accesses native format directly. +#if OS_IS_WINDOWS + // JUCE's moveImageSection() will crash under Direct2D so we use our own scrollImageSectionLeft(backImage, shift, width, height); - +#else + // On other platforms, we can use the built-in method + backImage.moveImageSection(0, 0, shift, 0, width - shift, height); +#endif backImage.clear(juce::Rectangle(width - shift, 0, shift, height), juce::Colours::transparentBlack); } @@ -371,16 +371,13 @@ class alignas(64) Oscilloscope : public juce::Thread private: //============================================================================== /** - * @brief Thread-safe pixel scroll operation for worker threads. + * @brief Non-crashing image scroll for Direct2D-backed images on Windows. * * @details - * Manually copies pixels left by shiftAmount using BitmapData, - * avoiding Direct2D's moveImageSection() which is not thread-safe - * when called from worker threads on Windows. - * - * On Direct2D-backed images, accessing BitmapData forces a software - * conversion, making this operation safe from any thread. - * On software images, this is just a fast memcpy. + * Because Windows is a piece of shit and JUCE's moveImageSection() will crash + * under Direct2D, we implement our own version that runs on the CPU. + * This way, we still get the fast Direct2D drawing without crashing the host + * DAWs on Windows. This solution is absolute garbage, but it is what it is. */ static void scrollImageSectionLeft(Image& image, int shiftAmount, @@ -413,9 +410,11 @@ class alignas(64) Oscilloscope : public juce::Thread dstData.getLinePointer(y), srcData.getLinePointer(y), lineSize); } } catch (const std::exception&) { - // BitmapData access failed; gracefully skip scroll - // (better than crashing from Direct2D race) - } + // In case of any exceptions (e.g., out-of-bounds), we simply skip the + // scroll to avoid crashing. The next render will correct any visual + // artifacts. + jassertfalse; // This should never happen, but we catch it just in case. + } } //==============================================================================