From 5621e8bf081be805c8846aac53c33e82d5ce24b0 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 27 Sep 2022 13:56:08 +0200 Subject: [PATCH] Add a serialization wrapper for streams --- src/common/serialization/clap/README.md | 1 + src/common/serialization/clap/stream.cpp | 117 +++++++++++++++++++++++ src/common/serialization/clap/stream.h | 92 ++++++++++++++++++ src/plugin/meson.build | 1 + src/wine-host/meson.build | 1 + 5 files changed, 212 insertions(+) create mode 100644 src/common/serialization/clap/stream.cpp create mode 100644 src/common/serialization/clap/stream.h diff --git a/src/common/serialization/clap/README.md b/src/common/serialization/clap/README.md index b037db3e..d8f8eaf0 100644 --- a/src/common/serialization/clap/README.md +++ b/src/common/serialization/clap/README.md @@ -9,6 +9,7 @@ Yabridge currently tracks CLAP 1.1.1. The implementation status for CLAP's core | core feature | status | | ----------------------------------------- | ------------------------------------------------------ | | Core plugin and host functionality | :warning: Everything but actual audio processing works | +| Streams | :heavy_check_mark: | | `clap.plugin-factory` | :heavy_check_mark: | | `clap.plugin-invalidation-factory/draft0` | :x: Not supported yet | diff --git a/src/common/serialization/clap/stream.cpp b/src/common/serialization/clap/stream.cpp new file mode 100644 index 00000000..b3e49b04 --- /dev/null +++ b/src/common/serialization/clap/stream.cpp @@ -0,0 +1,117 @@ +// yabridge: a Wine plugin bridge +// Copyright (C) 2020-2022 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more destates. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "stream.h" + +namespace clap { +namespace stream { + +/** + * We'll try to read the host's `clap_istream_t` in 1 MB chunks. + */ +constexpr size_t read_chunk_size = 1 << 20; + +Stream::Stream() {} + +Stream::Stream(const clap_istream_t& original) { + // CLAP streams have no length indication. A plugin could do something like + // prepending the stream's length to the stream, but we can't do that. So + // instead we'll try to read in 1 MB chunks until we reach end of file. Even + // if the stream's size is over 1 MB, the host may still return less than 1 + // MB at a time at its discretion. + size_t stream_length = 0; + while (true) { + // Start by reserving enough capacity to read 1 MB + buffer_.resize(stream_length + read_chunk_size); + const int64_t num_bytes_read = original.read( + &original, buffer_.data() + stream_length, read_chunk_size); + + // We're done when we reach the end of the file + if (num_bytes_read <= 0) { + break; + } + + stream_length += num_bytes_read; + } + + // Trim the excess reserved space + buffer_.resize(stream_length); +} + +const clap_ostream_t* Stream::ostream() { + ostream_vtable.write = ostream_write; + ostream_vtable.ctx = this; + + return &ostream_vtable; +} + +const clap_istream_t* Stream::istream() { + istream_vtable.read = istream_read; + istream_vtable.ctx = this; + + return &istream_vtable; +} + +void Stream::write_to_stream(const clap_ostream_t& original) const { + // The host may not let us write the whole stream all at once, so we need to + // keep track of how many bytes we've written and keep going until + // everything has been written back to the host. + size_t num_bytes_written = 0; + while (num_bytes_written < buffer_.size()) { + const int64_t actual_written_bytes = + original.write(&original, buffer_.data() + num_bytes_written, + buffer_.size() - num_bytes_written); + assert(actual_written_bytes > 0); + + num_bytes_written += actual_written_bytes; + } +} + +int64_t CLAP_ABI Stream::ostream_write(const struct clap_ostream* stream, + const void* buffer, + uint64_t size) { + assert(stream && stream->ctx && buffer); + auto self = static_cast(stream->ctx); + + // We can just read everything at the same time + const size_t start_pos = self->buffer_.size(); + self->buffer_.resize(start_pos + size); + std::copy_n(static_cast(buffer), size, + self->buffer_.data() + start_pos); + + return static_cast(size); +} + +int64_t CLAP_ABI Stream::istream_read(const struct clap_istream* stream, + void* buffer, + uint64_t size) { + assert(stream && stream->ctx && buffer); + auto self = static_cast(stream->ctx); + + // `self->read_pos` is a cursor in the buffer. CLAP streams always read from + // begin to end with no way to rewind. + const size_t num_bytes_read = std::min( + static_cast(size), self->buffer_.size() - self->read_pos); + + std::copy_n(self->buffer_.data() + self->read_pos, num_bytes_read, + static_cast(buffer)); + self->read_pos += num_bytes_read; + + return static_cast(num_bytes_read); +} + +} // namespace stream +} // namespace clap diff --git a/src/common/serialization/clap/stream.h b/src/common/serialization/clap/stream.h new file mode 100644 index 00000000..bb73a4d9 --- /dev/null +++ b/src/common/serialization/clap/stream.h @@ -0,0 +1,92 @@ +// yabridge: a Wine plugin bridge +// Copyright (C) 2020-2022 Robbert van der Helm +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more destates. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#pragma once + +#include + +#include +#include + +// Serialization messages for `clap/stream.h` + +namespace clap { +namespace stream { + +/** + * A serialization wrapper around streams that can be used as both a + * `clap_istream_t` and a `clap_ostream_t`. + */ +class Stream { + public: + /** + * Create an empty stream that can be written to by the plugin using + * `ostream()`, and then written back to the host using + * `write_to_ostream()`. + */ + Stream(); + + /** + * Read a `clap_istream_t` from the host to a buffer. The results are + * written to a buffer that can be serialized and send to the other side. + */ + Stream(const clap_istream_t& original); + + /** + * Get a `clap_ostream_t` for this buffer that the plugin can write to. This + * is only valid as long as this object is not moved. + */ + const clap_ostream_t* ostream(); + /** + * Get a `clap_istream_t` for this buffer that the plugin can read the + * buffer from. This is only valid as long as this object is not moved. + */ + const clap_istream_t* istream(); + + /** + * Write the entire buffer to a host provided `clap_ostream_t`. + */ + void write_to_stream(const clap_ostream_t& original) const; + + template + void serialize(S& s) { + s.container1b(buffer_, 50 << 20); + } + + protected: + static int64_t CLAP_ABI ostream_write(const struct clap_ostream* stream, + const void* buffer, + uint64_t size); + + static int64_t CLAP_ABI istream_read(const struct clap_istream* stream, + void* buffer, + uint64_t size); + + private: + std::vector buffer_; + + /** + * The current position in the buffer used in `istream_read()`. + */ + size_t read_pos = 0; + + // These are populated in the `ostream()` and `istream()` methods + clap_ostream_t ostream_vtable{}; + clap_istream_t istream_vtable{}; +}; + +} // namespace stream +} // namespace clap diff --git a/src/plugin/meson.build b/src/plugin/meson.build index c0139462..f8ad36e5 100644 --- a/src/plugin/meson.build +++ b/src/plugin/meson.build @@ -82,6 +82,7 @@ if with_clap '../common/serialization/clap/ext/params.cpp', '../common/serialization/clap/host.cpp', '../common/serialization/clap/plugin.cpp', + '../common/serialization/clap/stream.cpp', '../common/utils.cpp', '../include/llvm/small-vector.cpp', 'bridges/clap-impls/plugin-proxy.cpp', diff --git a/src/wine-host/meson.build b/src/wine-host/meson.build index 0a4beedd..d8f9f550 100644 --- a/src/wine-host/meson.build +++ b/src/wine-host/meson.build @@ -85,6 +85,7 @@ if with_clap '../common/serialization/clap/ext/params.cpp', '../common/serialization/clap/host.cpp', '../common/serialization/clap/plugin.cpp', + '../common/serialization/clap/stream.cpp', 'bridges/clap-impls/host-proxy.cpp', 'bridges/clap.cpp', )