diff --git a/Docs/ChangeLog-5x.md b/Docs/ChangeLog-5x.md index 7f346682..fff3ec7e 100644 --- a/Docs/ChangeLog-5x.md +++ b/Docs/ChangeLog-5x.md @@ -6,6 +6,25 @@ release of the 5.x series. All performance data on this page is measured on an Intel Core i5-9600K clocked at 4.2 GHz, running `astcenc` using AVX2 and 6 threads. + +## 5.2.0 + +**Status:** In development. + +The 5.2.0 release is a minor maintenance release. + +This release includes changes to the public interface in the `astcenc.h` +header. We always recommend rebuilding your client-side code using the +header from the same release to avoid compatibility issues. + +* **General:** + * **Feature:** Added a new codec API, `astcenc_compress_cancel()`, which can + be used to cancel an in-flight compression. This is designed to help make + it easier to integrate the codec into an interactive user interface that + can respond to user events with low latency. + * **Bug fix:** Removed incorrect `static` variable qualifier, which could + result in an incorrect `tune_mse_overshoot` heuristic threshold being used + if a user ran multiple concurrent compressions with different settings. ## 5.1.0 @@ -56,4 +75,4 @@ set. - - - -_Copyright © 2022-2024, Arm Limited and contributors. All rights reserved._ +_Copyright © 2022-2025, Arm Limited and contributors. All rights reserved._ diff --git a/Source/astcenc.h b/Source/astcenc.h index 3d04b4ea..8ecdc16f 100644 --- a/Source/astcenc.h +++ b/Source/astcenc.h @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 // ---------------------------------------------------------------------------- -// Copyright 2020-2024 Arm Limited +// Copyright 2020-2025 Arm Limited // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy @@ -784,6 +784,20 @@ ASTCENC_PUBLIC astcenc_error astcenc_compress_image( ASTCENC_PUBLIC astcenc_error astcenc_compress_reset( astcenc_context* context); +/** + * @brief Cancel any pending compression operation. + * + * The caller must behave as if the compression completed normally, even though the data will be + * undefined. They are still responsible for synchronizing threads in the worker thread pool, and + * must call reset before starting another compression. + * + * @param context Codec context. + * + * @return @c ASTCENC_SUCCESS on success, or an error if cancellation failed. + */ +ASTCENC_PUBLIC astcenc_error astcenc_compress_cancel( + astcenc_context* context); + /** * @brief Decompress an image. * diff --git a/Source/astcenc_entry.cpp b/Source/astcenc_entry.cpp index 5dc38016..4023797a 100644 --- a/Source/astcenc_entry.cpp +++ b/Source/astcenc_entry.cpp @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 // ---------------------------------------------------------------------------- -// Copyright 2011-2024 Arm Limited +// Copyright 2011-2025 Arm Limited // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy @@ -1123,6 +1123,29 @@ astcenc_error astcenc_compress_reset( #endif } +/* See header for documentation. */ +astcenc_error astcenc_compress_cancel( + astcenc_context* ctxo +) { +#if defined(ASTCENC_DECOMPRESS_ONLY) + (void)ctxo; + return ASTCENC_ERR_BAD_CONTEXT; +#else + astcenc_contexti* ctx = &ctxo->context; + if (ctx->config.flags & ASTCENC_FLG_DECOMPRESS_ONLY) + { + return ASTCENC_ERR_BAD_CONTEXT; + } + + // Cancel compression before cancelling avg. This avoids the race condition + // where cancelling them in the other order could see a compression worker + // starting to process even though some of the avg data is undefined. + ctxo->manage_compress.cancel(); + ctxo->manage_avg.cancel(); + return ASTCENC_SUCCESS; +#endif +} + /* See header for documentation. */ astcenc_error astcenc_decompress_image( astcenc_context* ctxo, diff --git a/Source/astcenc_internal_entry.h b/Source/astcenc_internal_entry.h index c283c5ac..966c1d31 100644 --- a/Source/astcenc_internal_entry.h +++ b/Source/astcenc_internal_entry.h @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 // ---------------------------------------------------------------------------- -// Copyright 2011-2024 Arm Limited +// Copyright 2011-2025 Arm Limited // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy @@ -100,6 +100,9 @@ class ParallelManager /** @brief Lock used for critical section and condition synchronization. */ std::mutex m_lock; + /** @brief True if the current operation is cancelled. */ + std::atomic m_is_cancelled; + /** @brief True if the stage init() step has been executed. */ bool m_init_done; @@ -147,6 +150,7 @@ class ParallelManager { m_init_done = false; m_term_done = false; + m_is_cancelled = false; m_start_count = 0; m_done_count = 0; m_task_count = 0; @@ -155,6 +159,16 @@ class ParallelManager m_callback_min_diff = 1.0f; } + /** + * @brief Clear the tracker and stop new tasks being assigned. + * + * Note, all in-flight tasks in a worker will still complete normally. + */ + void cancel() + { + m_is_cancelled = true; + } + /** * @brief Trigger the pipeline stage init step. * @@ -211,7 +225,7 @@ class ParallelManager unsigned int get_task_assignment(unsigned int granule, unsigned int& count) { unsigned int base = m_start_count.fetch_add(granule, std::memory_order_relaxed); - if (base >= m_task_count) + if (m_is_cancelled || base >= m_task_count) { count = 0; return 0; @@ -241,16 +255,17 @@ class ParallelManager local_count = m_done_count; local_last_value = m_callback_last_value; - if (m_done_count == m_task_count) + // Ensure the progress bar hits 100% + if (m_callback && m_done_count == m_task_count) { - // Ensure the progress bar hits 100% - if (m_callback) - { - std::unique_lock cblck(m_callback_lock); - m_callback(100.0f); - m_callback_last_value = 100.0f; - } + std::unique_lock cblck(m_callback_lock); + m_callback(100.0f); + m_callback_last_value = 100.0f; + } + // Notify if nothing left to do + if (m_is_cancelled || m_done_count == m_task_count) + { lck.unlock(); m_complete.notify_all(); } @@ -285,7 +300,7 @@ class ParallelManager void wait() { std::unique_lock lck(m_lock); - m_complete.wait(lck, [this]{ return m_done_count == m_task_count; }); + m_complete.wait(lck, [this]{ return m_is_cancelled || m_done_count == m_task_count; }); } /**