Skip to content
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

Adding draft extension for host-provided scratch memory #423

Merged
merged 19 commits into from
Nov 1, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,5 @@ cmake-build*/

# A place to store stuff and get it git ignored
ignore/*

.DS_Store
9 changes: 9 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
# Changes in 1.2.2

* [thread-check.h](include/clap/ext/thread-check.h): expand the thread-doc to clarify and expand realtime
* [latency.h](include/clap/ext/latency.h): adjust latency extension requirements
* [undo.h](include/clap/ext/draft/undo.h): re-design the interface
* the plugin interfaces have been separated into 2 independent ones
* the plugin interfaces are optional
* simplification of the design

# Changes in 1.2.1

## New draft extensions
Expand Down
63 changes: 63 additions & 0 deletions include/clap/ext/draft/scratch-memory.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#pragma once

#include "../../plugin.h"

// This extension lets the plugin request "scratch" memory
// from the host. Scratch memory can be accessed during the
// `process()` callback, but is not persistent between callbacks.
abique marked this conversation as resolved.
Show resolved Hide resolved
jatinchowdhury18 marked this conversation as resolved.
Show resolved Hide resolved
//
// The motivation for this extension is to allow the plugin host
// to "share" a single scratch buffer across multiple plugin
// instances.
//
// For example, imagine the host needs to process three plugins
// in sequence, and each plugin requires 10K of scratch memory.
// If each plugin pre-allocates its own scratch memory, then 30K
// of memory is being allocated in total. However, if each plugin
// requests 10K of scratch memory from the host, then the host can
// allocate a single 10K scratch buffer, and make it available to all
// three plugins.
//
// On memory-constrained platforms, this optimization may allow for
// more plugins to be used simultaneously. On platforms with lots
// of memory, this optimization may improve CPU cache usage.
jatinchowdhury18 marked this conversation as resolved.
Show resolved Hide resolved

static CLAP_CONSTEXPR const char CLAP_EXT_SCRATCH_MEMORY[] = "clap.scratch-memory/1";

#ifdef __cplusplus
extern "C" {
#endif

typedef struct clap_host_scratch_memory {
// Asks the host for certain amount of scratch memory.
// If the host is unable to provide the memory, it should
// return "false".
//
// Note that any memory the host allocates to satisfy
// the requested scratch size can be de-allocated by the
// host when the plugin is de-activated.
jatinchowdhury18 marked this conversation as resolved.
Show resolved Hide resolved
jatinchowdhury18 marked this conversation as resolved.
Show resolved Hide resolved
//
// [main-thread & being-activated]
jatinchowdhury18 marked this conversation as resolved.
Show resolved Hide resolved
bool(CLAP_ABI *pre_reserve)(const clap_host_t *host, size_t scratch_size_bytes);
abique marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we ever use size_t in any other extension? -> uint?_t ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, uint32_t would do the job I think.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use uint64_t which is size_t in most of our systems? The host has the option to return no for values out of bounds

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have a preference for uint32_t vs uint64_t... but Trinitou is right that CLAP doesn't use size_t anywhere else, so I don't think we should use it here.

Copy link
Contributor

@abique abique Oct 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A scratch size bigger than 4 GB would be problematic I suppose, remember nthreads * max_scratch_size.
Anyway regardless of the type, many host will likely have their own threshold.
uint32_t seems sufficient to me, but I'm happy with uint64_t as well.


// Asks the host for the previously requested scratch memory.
// If the host returned "true" when scratch memory was requested,
// then this method must return a pointer to a memory block at least
// as large as the requested size. If the host returned "false"
// when scratch memory was requested then this method should return
// null.
jatinchowdhury18 marked this conversation as resolved.
Show resolved Hide resolved
//
// The provided memory is not initialized, and may have been used
// by other plugin instances, so the plugin must correctly initialize
// the memory when using it.
//
// The provided memory is owned by the host, so the plugin should not
// attempt to free the memory.
//
// [audio-thread]
void*(CLAP_ABI *access)(const clap_host_t *host);
} clap_host_scratch_memory_t;

#ifdef __cplusplus
}
#endif
142 changes: 90 additions & 52 deletions include/clap/ext/draft/undo.h
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
#pragma once

#include "../../plugin.h"
#include "../../stream.h"

static CLAP_CONSTEXPR const char CLAP_EXT_UNDO[] = "clap.undo/2";
static CLAP_CONSTEXPR const char CLAP_EXT_UNDO[] = "clap.undo/4";
static CLAP_CONSTEXPR const char CLAP_EXT_UNDO_CONTEXT[] = "clap.undo_context/4";
static CLAP_CONSTEXPR const char CLAP_EXT_UNDO_DELTA[] = "clap.undo_delta/4";

#ifdef __cplusplus
extern "C" {
Expand All @@ -15,7 +18,7 @@ extern "C" {
///
/// Calling host->undo() or host->redo() is equivalent to clicking undo/redo within the host's GUI.
///
/// If the plugin implements this interface then its undo and redo should be entirely delegated to
/// If the plugin uses this interface then its undo and redo should be entirely delegated to
/// the host; clicking in the plugin's UI undo or redo is equivalent to clicking undo or redo in the
/// host's UI.
///
Expand All @@ -40,31 +43,32 @@ extern "C" {
/// history. This simplifies the host implementation, leading to less bugs, a more robust design
/// and maybe an easier experience for the user because there's a single undo context versus one
/// for the host and one for each plugin instance.

enum clap_undo_context_flags {
// While the host is within a change, it is impossible to perform undo or redo.
CLAP_UNDO_IS_WITHIN_CHANGE = 1 << 0,
};

enum clap_undo_delta_properties_flags {
// If not set, then all clap_undo_delta_properties's attributes become irrelevant.
// If set, then the plugin will provide deltas in host->change_made().
CLAP_UNDO_DELTA_PROPERTIES_HAS_DELTA = 1 << 0,

// If set, then the delta will be reusable in the future as long as the plugin is
// compatible with the given format_version.
CLAP_UNDO_DELTA_PROPERTIES_IS_PERSISTENT = 1 << 1,
};
///
/// This extension tries to make it as easy as possible for the plugin to hook into the host undo
/// and make it efficient when possible by using deltas. The plugin interfaces are all optional, and
/// the plugin can for a minimal implementation, just use the host interface and call
/// host->change_made() without providing a delta. This is enough for the host to know that it can
/// capture a plugin state for the undo step.

typedef struct clap_undo_delta_properties {
// Bitmask of clap_undo_delta_properties_flags
uint64_t flags;
// If true, then the plugin will provide deltas in host->change_made().
// If false, then all clap_undo_delta_properties's attributes become irrelevant.
bool has_delta;

// If true, then the deltas can be stored on disk and re-used in the future as long as the plugin
// is compatible with the given format_version.
//
// If false, then format_version must be set to CLAP_INVALID_ID.
bool are_deltas_persistent;

// This represents the delta format version that the plugin is using.
uint32_t format_version;
// This represents the delta format version that the plugin is currently using.
// Use CLAP_INVALID_ID for invalid value.
clap_id format_version;
} clap_undo_delta_properties_t;

typedef struct clap_plugin_undo {
// Use CLAP_EXT_UNDO_DELTA.
// This is an optional interface, using deltas is an optimization versus making a state snapshot.
typedef struct clap_plugin_undo_delta {
// Asks the plugin the delta properties.
// [main-thread]
void(CLAP_ABI *get_delta_properties)(const clap_plugin_t *plugin,
Expand All @@ -76,25 +80,42 @@ typedef struct clap_plugin_undo {
bool(CLAP_ABI *can_use_delta_format_version)(const clap_plugin_t *plugin,
clap_id format_version);

// Applies synchronously a delta.
// Undo using the delta.
// Returns true on success.
//
// [main-thread]
bool(CLAP_ABI *apply_delta)(const clap_plugin_t *plugin,
clap_id format_version,
const void *delta,
size_t delta_size);

// Sets the undo context.
// flags: bitmask of clap_undo_context_flags values
// names: null terminated string if an redo/undo step exists, null otherwise.
// [main-thread]
void(CLAP_ABI *set_context_info)(const clap_plugin_t *plugin,
uint64_t flags,
const char *undo_name,
const char *redo_name);
} clap_plugin_undo_t;
bool(CLAP_ABI *undo)(const clap_plugin_t *plugin,
clap_id format_version,
const void *delta,
size_t delta_size);

// Redo using the delta.
// Returns true on success.
//
// [main-thread]
bool(CLAP_ABI *redo)(const clap_plugin_t *plugin,
clap_id format_version,
const void *delta,
size_t delta_size);
} clap_plugin_undo_delta_t;

// Use CLAP_EXT_UNDO_CONTEXT.
// This is an optional interface, that the plugin can implement in order to know about
// the current undo context.
typedef struct clap_plugin_undo_context {
// Indicate if it is currently possible to perform an undo or redo operation.
// [main-thread & plugin-subscribed-to-undo-context]
void(CLAP_ABI *set_can_undo)(const clap_plugin_t *plugin, bool can_undo);
void(CLAP_ABI *set_can_redo)(const clap_plugin_t *plugin, bool can_redo);

// Sets the name of the next undo or redo step.
// name: null terminated string.
// [main-thread & plugin-subscribed-to-undo-context]
void(CLAP_ABI *set_undo_name)(const clap_plugin_t *plugin, const char *name);
void(CLAP_ABI *set_redo_name)(const clap_plugin_t *plugin, const char *name);
} clap_plugin_undo_context_t;

// Use CLAP_EXT_UNDO.
typedef struct clap_host_undo {
// Begins a long running change.
// The plugin must not call this twice: there must be either a call to cancel_change() or
Expand All @@ -112,36 +133,51 @@ typedef struct clap_host_undo {
//
// name: mandatory null terminated string describing the change, this is displayed to the user
//
// deltas: optional, they are binary blobs used to perform the undo and redo. When not available
// delta: optional, it is a binary blobs used to perform the undo and redo. When not available
// the host will save the plugin state and use state->load() to perform undo and redo.
// The plugin must be able to perform a redo operation using the delta, though the undo operation
// is only possible if delta_can_undo is true.
//
// Note: the provided delta may be used for incremental state saving and crash recovery. The
// plugin can indicate a format version id and the validity lifetime for the binary blobs.
// The host can use these to verify the compatibility before applying the delta.
// If the plugin is unable to use a delta, a notification should be provided to the user and
// the crash recovery should perform a best effort job, at least restoring the latest saved state.
// the crash recovery should perform a best effort job, at least restoring the latest saved
// state.
//
// Special case: for objects with shared and synchronized state, changes shouldn't be reported
// as the host already knows about it.
// For example, plugin parameter changes shouldn't produce a call to change_made().
//
// Note: if the plugin asked for this interface, then host_state->mark_dirty() will not create an
// implicit undo step.
//
// Note: if the plugin did load a preset or did something that leads to a large delta,
// it may consider not producing a delta (pass null) and let the host make a state snapshot
// instead.
//
// Note: if a plugin is producing a lot of changes within a small amount of time, the host
// may merge them into a single undo step.
//
// [main-thread]
void(CLAP_ABI *change_made)(const clap_host_t *host,
const char *name,
const void *redo_delta,
size_t redo_delta_size,
const void *undo_delta,
size_t undo_delta_size);

// Asks the host to perform the next undo step.
// This operation may be asynchronous.
// [main-thread]
void(CLAP_ABI *undo)(const clap_host_t *host);
const void *delta,
size_t delta_size,
bool delta_can_undo);

// Asks the host to perform the next redo step.
// This operation may be asynchronous.
// Asks the host to perform the next undo or redo step.
//
// Note: this maybe a complex and asynchronous operation, which may complete after
// this function returns.
//
// Note: the host may ignore this request if there is no undo/redo step to perform,
// or if the host is unable to perform undo/redo at the time (eg: a long running
// change is going on).
//
// [main-thread]
void(CLAP_ABI *redo)(const clap_host_t *host);
void(CLAP_ABI *request_undo)(const clap_host_t *host);
void(CLAP_ABI *request_redo)(const clap_host_t *host);

// Subscribes to or unsubscribes from undo context info.
//
Expand All @@ -154,8 +190,10 @@ typedef struct clap_host_undo {
//
// is_subscribed: set to true to receive context info
//
// It is mandatory for the plugin to implement CLAP_EXT_UNDO_CONTEXT when using this method.
//
// [main-thread]
void(CLAP_ABI *set_context_info_subscription)(const clap_host_t *host, bool is_subscribed);
void(CLAP_ABI *set_wants_context_updates)(const clap_host_t *host, bool is_subscribed);
} clap_host_undo_t;

#ifdef __cplusplus
Expand Down
6 changes: 3 additions & 3 deletions include/clap/ext/latency.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ extern "C" {

typedef struct clap_plugin_latency {
// Returns the plugin latency in samples.
// [main-thread & active]
// [main-thread & (being-activated | active)]
uint32_t(CLAP_ABI *get)(const clap_plugin_t *plugin);
} clap_plugin_latency_t;

typedef struct clap_host_latency {
// Tell the host that the latency changed.
// The latency is only allowed to change if the plugin is deactivated.
// The latency is only allowed to change during plugin->activate.
// If the plugin is activated, call host->request_restart()
// [main-thread]
// [main-thread & being-activated]
void(CLAP_ABI *changed)(const clap_host_t *host);
} clap_host_latency_t;

Expand Down
47 changes: 34 additions & 13 deletions include/clap/ext/thread-check.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,44 @@ extern "C" {
/// This will be the same OS thread throughout the lifetime of the plug-in.
/// On macOS and Windows, this must be the thread on which gui and timer events are received
/// (i.e., the main thread of the program).
/// It isn't a realtime thread, yet this thread needs to respond fast enough to user interaction,
/// so it is recommended to run long and expensive tasks such as preset indexing or asset loading
/// in dedicated background threads.
/// It isn't a realtime thread, yet this thread needs to respond fast enough to allow responsive
/// user interaction, so it is strongly recommended plugins run long,and expensive or blocking
/// tasks such as preset indexing or asset loading in dedicated background threads started by the
/// plugin.
///
/// audio-thread:
/// This thread is used for realtime audio processing. Its execution should be as deterministic
/// as possible to meet the audio interface's deadline (can be <1ms). In other words, there is a
/// known set of operations that should be avoided: malloc() and free(), mutexes (spin mutexes
/// are worse), I/O, waiting, ...
/// The audio-thread is something symbolic, there isn't one OS thread that remains the
/// audio-thread for the plugin lifetime. As you may guess, the host is likely to have a
/// This thread can be used for realtime audio processing. Its execution should be as
/// deterministic as possible to meet the audio interface's deadline (can be <1ms). There are a
/// known set of operations that should be avoided: malloc() and free(), contended locks and
/// mutexes, I/O, waiting, and so forth.
///
/// The audio-thread is symbolic, there isn't one OS thread that remains the
/// audio-thread for the plugin lifetime. A host is may opt to have a
/// thread pool and the plugin.process() call may be scheduled on different OS threads over time.
/// The most important thing is that there can't be two audio-threads at the same time. All the
/// functions marked with [audio-thread] **ARE NOT CONCURRENT**. The host may mark any OS thread,
/// However, the host must guarantee that single plugin instance will not be two audio-threads
/// at the same time.
///
/// Functions marked with [audio-thread] **ARE NOT CONCURRENT**. The host may mark any OS thread,
/// including the main-thread as the audio-thread, as long as it can guarantee that only one OS
/// thread is the audio-thread at a time. The audio-thread can be seen as a concurrency guard for
/// all functions marked with [audio-thread].
/// thread is the audio-thread at a time in a plugin instance. The audio-thread can be seen as a
/// concurrency guard for all functions marked with [audio-thread].
///
/// The real-time constraint on the [audio-thread] interacts closely with the render extension.
/// If a plugin doesn't implement render, then that plugin must have all [audio-thread] functions
/// meet the real time standard. If the plugin does implement render, and returns true when
/// render mode is set to real-time or if the plugin advertises a hard realtime requirement, it
/// must implement realtime constraints. Hosts also provide functions marked [audio-thread].
/// These can be safely called by a plugin in the audio thread. Therefore hosts must either (1)
/// implement those functions meeting the real-time constraints or (2) not process plugins which
/// advertise a hard realtime constraint or don't implement the render extension. Hosts which
/// provide [audio-thread] functions outside these conditions may experience inconsistent or
/// inaccurate rendering.
///
/// Clap also tags some functions as [thread-safe]. Functions tagged as [thread-safe] can be called
/// from any thread unless explicitly counter-indicated (for instance [thread-safe, !audio-thread])
/// and may be called concurrently. Since a [thread-safe] function may be called from the
/// [audio-thread] unless explicitly counter-indicated, it must also meet the realtime constraints
/// as describes above.

// This interface is useful to do runtime checks and make
// sure that the functions are called on the correct threads.
Expand Down
6 changes: 6 additions & 0 deletions include/clap/host.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ typedef struct clap_host {
void(CLAP_ABI *request_process)(const struct clap_host *host);

// Request the host to schedule a call to plugin->on_main_thread(plugin) on the main thread.
// This callback should be called as soon as practicable, usually in the host application's next
// available main thread time slice. Typically callbacks occur withink 33ms / 30hz.
// Despite this guidance, plugins should not make assumptions about the exactness of timing for
// a main thread callback, but hosts should endeavour to be prompt. For example, in high load situations
// the environment may starve the gui/main thread in favor of audio processing, leading to substantially
// longer latencies for the callback than the indicative times given here.
// [thread-safe]
void(CLAP_ABI *request_callback)(const struct clap_host *host);
} clap_host_t;
Expand Down
2 changes: 1 addition & 1 deletion include/clap/version.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ typedef struct clap_version {

#define CLAP_VERSION_MAJOR 1
#define CLAP_VERSION_MINOR 2
#define CLAP_VERSION_REVISION 1
#define CLAP_VERSION_REVISION 2

#define CLAP_VERSION_INIT \
{ (uint32_t)CLAP_VERSION_MAJOR, (uint32_t)CLAP_VERSION_MINOR, (uint32_t)CLAP_VERSION_REVISION }
Expand Down
Loading