Skip to content

Add codec API to cancel a compression #536

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion Docs/ChangeLog-5x.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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._
16 changes: 15 additions & 1 deletion Source/astcenc.h
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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.
*
Expand Down
25 changes: 24 additions & 1 deletion Source/astcenc_entry.cpp
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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,
Expand Down
37 changes: 26 additions & 11 deletions Source/astcenc_internal_entry.h
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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<bool> m_is_cancelled;

/** @brief True if the stage init() step has been executed. */
bool m_init_done;

Expand Down Expand Up @@ -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;
Expand All @@ -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.
*
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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<std::mutex> cblck(m_callback_lock);
m_callback(100.0f);
m_callback_last_value = 100.0f;
}
std::unique_lock<std::mutex> 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();
}
Expand Down Expand Up @@ -285,7 +300,7 @@ class ParallelManager
void wait()
{
std::unique_lock<std::mutex> 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; });
}

/**
Expand Down