新增taskflow, 新增单例管理, 新增从Slate版本迁移的部分功能

This commit is contained in:
Nanako 2024-02-27 00:38:00 +08:00
parent 2f80e79800
commit 8b328cc0d6
56 changed files with 4991 additions and 38 deletions

3
.gitmodules vendored
View File

@ -10,3 +10,6 @@
[submodule "third_party/glfw"]
path = third_party/glfw
url = https://github.com/glfw/glfw.git
[submodule "third_party/taskflow"]
path = third_party/taskflow
url = https://github.com/taskflow/taskflow.git

View File

@ -96,6 +96,8 @@ add_subdirectory(third_party/imgui)
add_subdirectory(third_party/portaudio)
add_subdirectory(third_party/spdlog)
add_subdirectory(third_party/glfw)
add_subdirectory(third_party/mempool)
add_subdirectory(third_party/taskflow)
# setup portaudio
set(PA_USE_ASIO ON CACHE BOOL "" FORCE)
@ -121,6 +123,10 @@ set(SPDLOG_WCHAR_SUPPORT ON CACHE BOOL "" FORCE)
set(SPDLOG_ENABLE_PCH ON CACHE BOOL "" FORCE)
set(SPDLOG_USE_STD_FORMAT OFF CACHE BOOL "" FORCE)
# setup taskflow
set(TF_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(TF_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
add_definitions(-DGLFW_INCLUDE_NONE)
# install

View File

@ -8,10 +8,10 @@ retrieve_files(ALL_FILES)
add_library(${PROJECT_NAME} SHARED ${ALL_FILES})
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} spdlog imgui glfw)
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} PortAudio spdlog imgui glfw mempool Taskflow)
target_link_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} spdlog imgui glfw)
target_link_libraries(${PROJECT_NAME} PUBLIC imgui spdlog glfw)
target_link_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} PortAudio spdlog imgui glfw mempool Taskflow)
target_link_libraries(${PROJECT_NAME} PUBLIC PortAudio imgui spdlog glfw mempool Taskflow)
target_precompile_headers(${PROJECT_NAME} PUBLIC extern.h)
add_definitions(-Dcore_EXPORTS -DSTB_IMAGE_IMPLEMENTATION -DSTBI_WINDOWS_UTF8)

View File

@ -48,8 +48,11 @@ void application::init(const window_params& in_window_params, int argc, char** a
}
int application::run() {
const ImGuiIO& io = ImGui::GetIO();
while (!g_exit_requested) {
glfwPollEvents();
g_exit_requested = g_exit_requested || glfwWindowShouldClose(window_);
tick(io.DeltaTime);
renderer_->new_frame(window_);
draw_gui();
renderer_->end_frame(window_);
@ -71,7 +74,7 @@ void application::shutdown() {
g_is_running = false;
}
std::shared_ptr<texture> application::load_texture(const std::string& path, vk::Format format) const {
std::shared_ptr<texture> application::load_texture(const std::string& path, vk::Format format) {
int width = 0;
int height = 0;
uint8_t* image_data = stbi_load(path.c_str(), &width, &height, nullptr, 4);
@ -84,27 +87,17 @@ std::shared_ptr<texture> application::load_texture(const std::string& path, vk::
return texture;
}
std::shared_ptr<texture>
application::create_texture(const unsigned char* data, const int width, const int height, vk::Format format) const {
std::shared_ptr<texture> application::create_texture(const unsigned char* data, const int width, const int height, vk::Format format) {
return renderer::create_texture(data, width, height, format);
}
std::shared_ptr<render_target> application::create_render_target(const int width, const int height,
vk::Format format) const {
return renderer::create_render_target(width, height, format);
}
std::shared_ptr<pixel_shader_drawer> application::create_pixel_shader_drawer() const {
return nullptr;
}
void application::init_glfw() {
glfwSetErrorCallback(glfw_error_callback);
if (!glfwInit()) {
spdlog::error("Failed to initialize GLFW");
return;
}
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
// glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
}
void application::init_imgui() {
@ -116,8 +109,8 @@ void application::init_imgui() {
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking
io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows
io.ConfigViewportsNoAutoMerge = true;
io.ConfigViewportsNoTaskBarIcon = false;
// io.ConfigViewportsNoAutoMerge = true;
// io.ConfigViewportsNoTaskBarIcon = false;
ImGui::StyleColorsDark();
//ImGui::StyleColorsLight();
}

View File

@ -5,7 +5,6 @@
#include <vulkan/vulkan.hpp>
class renderer;
class pixel_shader_drawer;
class render_target;
class texture;
class application;
@ -55,19 +54,14 @@ public:
}
virtual void draw_gui() = 0;
virtual void tick(float delta_time) {}
virtual const char* get_shader_path() = 0;
virtual void init_imgui(ImGuiContext* in_context) = 0;
[[nodiscard]] std::shared_ptr<texture> load_texture(const std::string& path, vk::Format format = vk::Format::eR8G8B8A8Unorm) const;
std::shared_ptr<texture> create_texture(const unsigned char* data, int width, int height, vk::Format format) const;
[[nodiscard]] std::shared_ptr<render_target> create_render_target(int width, int height,
vk::Format format) const;
[[nodiscard]] std::shared_ptr<pixel_shader_drawer> create_pixel_shader_drawer() const;
[[nodiscard]] static std::shared_ptr<texture> load_texture(const std::string& path, vk::Format format = vk::Format::eR8G8B8A8Unorm);
[[nodiscard]] static std::shared_ptr<texture> create_texture(const unsigned char* data, int width, int height, vk::Format format);
[[nodiscard]] virtual const char* get_entry_model() const = 0;

View File

@ -0,0 +1 @@
#include "audio_device.h"

View File

@ -0,0 +1,29 @@
#pragma once
class audio_device {
public:
virtual ~audio_device() = default;
virtual bool init() = 0;
virtual void destroy() = 0;
void set_sample_rate(double sample_rate) {
if (on_set_sample_rate(sample_rate)) {
sample_rate_ = sample_rate;
}
}
void set_buffer_size(uint32_t buffer_size) {
if (on_set_buffer_size(buffer_size)) {
buffer_size_ = buffer_size;
}
}
[[nodiscard]] double sample_rate() const { return sample_rate_; }
[[nodiscard]] uint32_t buffer_size() const { return buffer_size_; }
protected:
virtual bool on_set_sample_rate(double in_sample_rate) = 0;
virtual bool on_set_buffer_size(uint32_t in_buffer_size) = 0;
private:
double sample_rate_ = 44100;
uint32_t buffer_size_ = 512;
};

View File

@ -0,0 +1 @@
#include "dummy_audio_device.h"

View File

@ -0,0 +1,12 @@
#pragma once
#include "audio_device.h"
class dummy_audio_device : public audio_device {
public:
bool init() override { return true; }
void destroy() override {}
protected:
bool on_set_buffer_size(uint32_t in_buffer_size) override { return true; }
bool on_set_sample_rate(double in_sample_rate) override { return true; }
};

View File

@ -0,0 +1,127 @@
#include "port_audio_device.h"
#include "audio/mixer/mixer.h"
#include "audio/mixer/mixer_track.h"
#include "audio/plugin_host/midi_sequencer.h"
#include "audio/plugin_host/plugin_host_manager.h"
#include "thread_message/thread_message_hubs.h"
int port_audio_callback(const void* Input, void* Output, unsigned long FrameCount, const PaStreamCallbackTimeInfo* TimeInfo, PaStreamCallbackFlags StatusFlags, void* UserData)
{
auto* API = static_cast<port_audio_device*>(UserData);
return API->stream_callback((float**)Input, static_cast<float**>(Output), FrameCount, TimeInfo, StatusFlags);
}
port_audio_device::~port_audio_device() {
port_audio_device::destroy();
}
bool port_audio_device::init() {
Pa_Initialize();
try {
open_stream(0, Pa_GetDefaultOutputDevice(), sample_rate(), buffer_size());
} catch (const std::exception& e) {
spdlog::error("Init failed: {}", e.what());
return false;
}
return true;
}
void port_audio_device::destroy() {
stop();
Pa_Terminate();
}
void port_audio_device::stop() {
Pa_StopStream(stream_);
Pa_CloseStream(stream_);
stream_ = nullptr;
render_thread_.join();
}
void port_audio_device::open_stream(uint32_t input_device, uint32_t output_device, double in_sample_rate, uint32_t in_buffer_size) {
PaStreamParameters* input_params = nullptr;
if (input_device > 0) {
input_params_.device = input_device;
input_params_.channelCount = 2;
input_params_.sampleFormat = paFloat32 | paNonInterleaved;
input_params_.suggestedLatency = Pa_GetDeviceInfo(input_device)->defaultLowInputLatency;
input_params_.hostApiSpecificStreamInfo = nullptr;
input_params = &input_params_;
}
output_params_.device = output_device;
output_params_.channelCount = 2;
output_params_.sampleFormat = paFloat32 | paNonInterleaved;
output_params_.suggestedLatency = Pa_GetDeviceInfo(output_device)->defaultLowOutputLatency;
output_params_.hostApiSpecificStreamInfo = nullptr;
PaError err = Pa_OpenStream(&stream_, input_params, &output_params_, in_sample_rate, in_buffer_size, paClipOff, port_audio_callback, this);
if (err != paNoError) {
throw std::runtime_error(Pa_GetErrorText(err));
}
err = Pa_StartStream(stream_);
if (err != paNoError) {
throw std::runtime_error(Pa_GetErrorText(err));
}
render_thread_ = std::thread(&port_audio_device::render_thread, this);
render_thread_.detach();
}
int port_audio_device::stream_callback(float** input, float** output, unsigned long frame_count,
const PaStreamCallbackTimeInfo* time_info, PaStreamCallbackFlags status_flags) {
if (render_buffer_.Num() < frame_count * output_params_.channelCount)
return paContinue;
for (int i = 0; i < output_params_.channelCount; ++i) {
render_buffer_.Pop(input[i], frame_count);
}
return paContinue;
}
bool port_audio_device::on_set_buffer_size(uint32_t in_buffer_size) {
stop();
try {
open_stream(input_params_.device, output_params_.device, sample_rate(), in_buffer_size);
} catch (const std::exception& e) {
spdlog::error("Set buffer size failed: {}", e.what());
return false;
}
return true;
}
bool port_audio_device::on_set_sample_rate(double in_sample_rate) {
stop();
try {
open_stream(input_params_.device, output_params_.device, in_sample_rate, buffer_size());
} catch (const std::exception& e) {
spdlog::error("Set sample rate failed: {}", e.what());
return false;
}
return true;
}
void port_audio_device::render_thread() {
dummy_track* master = g_mixer.get_master();
while (stream_) {
const uint32_t frames = buffer_size();
const double rate = sample_rate();
const uint32_t milliseconds = 1.f / (rate / (frames / 4)) * 1000;
g_audio_thread_hub.process_messages();
if (render_buffer_.Num() >= render_buffer_size_) {
std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds));
continue;
}
g_midi_sequencer.process(rate, frames);
g_mixer.reset();
g_plugin_host_manager.process(frames);
g_mixer.process(frames);
const auto& master_headers = master->buffer.get_headers_vector();
for (const auto element: master_headers) {
render_buffer_.Push(element, frames);
}
}
}

View File

@ -0,0 +1,33 @@
#pragma once
#include "audio_device.h"
#include "portaudio.h"
#include "audio/misc/circular_audio_buffer.h"
class port_audio_device : public audio_device {
public:
~port_audio_device() override;
bool init() override;
void destroy() override;
void stop();
void open_stream(uint32_t input_device, uint32_t output_device, double in_sample_rate, uint32_t in_buffer_size);
int stream_callback(float** input, float** output, unsigned long frame_count, const PaStreamCallbackTimeInfo* time_info, PaStreamCallbackFlags status_flags);
protected:
bool on_set_buffer_size(uint32_t in_buffer_size) override;
bool on_set_sample_rate(double in_sample_rate) override;
private:
#pragma region render_thread
void render_thread();
std::thread render_thread_;
circular_audio_buffer<float> render_buffer_;
uint32_t render_buffer_size_ = 0;
#pragma endregion
PaStreamParameters input_params_ = {};
PaStreamParameters output_params_ = {};
PaStream* stream_ = nullptr;
};

View File

@ -0,0 +1,40 @@
#include "audio_buffer.h"
void audio_buffer::resize(uint32_t channel_num, uint32_t block_size) {
std::scoped_lock lock(lock_);
buffer_.resize(channel_num);
headers_.resize(channel_num);
for (uint32_t i = 0; i < channel_num; i++) {
buffer_[i].resize(block_size);
headers_[i] = buffer_[i].data();
}
}
void audio_buffer::clear() {
std::scoped_lock lock(lock_);
for (auto& channel : buffer_) {
std::fill(channel.begin(), channel.end(), 0.f);
}
}
void audio_buffer::mix(audio_buffer& in_buffer, float percent) {
std::scoped_lock lock(lock_);
// will be optimized by compiler
for (uint32_t channel_index = 0; channel_index < buffer_.size(); channel_index++) {
auto& channel = buffer_[channel_index];
auto& in_channel = in_buffer.buffer_[channel_index];
for (uint32_t sample_index = 0; sample_index < channel.size(); sample_index++) {
channel[sample_index] += in_channel[sample_index] * percent;
}
}
}
void audio_buffer::multiple(float percent) {
std::scoped_lock lock(lock_);
// will be optimized by compiler
for (auto& channel : buffer_) {
for (auto& sample : channel) {
sample *= percent;
}
}
}

View File

@ -0,0 +1,21 @@
#pragma once
class audio_buffer {
public:
float** get_headers() { return headers_.data(); }
const std::vector<float*>& get_headers_vector() { return headers_; }
[[nodiscard]] uint32_t get_num_channels() const { return buffer_.size(); }
[[nodiscard]] uint32_t get_num_samples() const { return buffer_[0].size(); }
void resize(uint32_t channel_num, uint32_t block_size);
void clear();
void mix(audio_buffer& in_buffer, float percent = 1.f);
void multiple(float percent);
private:
std::vector<std::vector<float>> buffer_;
std::vector<float*> headers_;
std::mutex lock_;
};

View File

@ -0,0 +1,268 @@
#pragma once
#include <vector>
#include <atomic>
#include "extern.h"
/**
* Basic implementation of a circular buffer built for pushing and popping arbitrary amounts of data at once.
* Designed to be thread safe for SPSC; However, if Push() and Pop() are both trying to access an overlapping area of the buffer,
* One of the calls will be truncated. Thus, it is advised that you use a high enough capacity that the producer and consumer are never in contention.
*/
template<typename SampleType>
class circular_audio_buffer {
private:
std::vector<SampleType> InternalBuffer;
uint32_t Capacity;
std::atomic<int32_t> ReadCounter;
std::atomic<int32_t> WriteCounter;
public:
circular_audio_buffer() {
SetCapacity(0);
}
circular_audio_buffer(const circular_audio_buffer& InOther) {
*this = InOther;
}
circular_audio_buffer& operator=(const circular_audio_buffer& InOther) {
InternalBuffer = InOther.InternalBuffer;
Capacity = InOther.Capacity;
ReadCounter = InOther.ReadCounter;
WriteCounter = InOther.WriteCounter;
return *this;
}
circular_audio_buffer(uint32_t InCapacity) {
SetCapacity(InCapacity);
}
void Reset(uint32_t InCapacity = 0) {
SetCapacity(InCapacity);
}
void SetCapacity(uint32_t InCapacity) {
if (!(InCapacity < (uint32_t)2147483647i32)) { spdlog::error("Max capacity for this buffer is 2,147,483,647 samples. Otherwise our index arithmetic will not work."); throw std::runtime_error(fmt::format("Max capacity for this buffer is 2,147,483,647 samples. Otherwise our index arithmetic will not work.")); };
Capacity = InCapacity + 1;
ReadCounter = 0;
WriteCounter = 0;
InternalBuffer.Reset();
InternalBuffer.AddZeroed(Capacity);
}
/** Reserve capacity.
*
* @param InMinimumCapacity - Minimum capacity of circular buffer.
* @param bRetainExistingSamples - If true, existing samples in the buffer will be retained. If false, they are discarded.
*/
void Reserve(uint32_t InMinimumCapacity, bool bRetainExistingSamples) {
if (Capacity <= InMinimumCapacity) {
uint32_t NewCapacity = InMinimumCapacity + 1;
checkf(NewCapacity < (uint32_t)INT32_MAX,
"Max capacity overflow. Requested {}. Maximum allowed {}", NewCapacity,
INT32_MAX);
uint32_t NumToAdd = NewCapacity - Capacity;
InternalBuffer.AddZeroed(NumToAdd);
Capacity = NewCapacity;
}
if (!bRetainExistingSamples) {
ReadCounter = 0;
WriteCounter = 0;
}
}
/** Push an array of values into circular buffer. */
int32_t Push(std::vector<const SampleType> InBuffer) {
return Push(InBuffer.data(), InBuffer.size());
}
// Pushes some amount of samples into this circular buffer.
// Returns the amount of samples written.
// This can only be used for trivially copyable types.
int32_t Push(const SampleType* InBuffer, uint32_t NumSamples) {
SampleType* DestBuffer = InternalBuffer.data();
const uint32_t ReadIndex = ReadCounter;
const uint32_t WriteIndex = WriteCounter;
int32_t NumToCopy = std::min<int32_t>(NumSamples, Remainder());
const int32_t NumToWrite = std::min<int32_t>(NumToCopy, Capacity - WriteIndex);
memcpy(&DestBuffer[WriteIndex], InBuffer, NumToWrite * sizeof(SampleType));
memcpy(&DestBuffer[0], &InBuffer[NumToWrite], (NumToCopy - NumToWrite) * sizeof(SampleType));
WriteCounter = (WriteIndex + NumToCopy) % Capacity;
return NumToCopy;
}
// Pushes some amount of zeros into the circular buffer.
// Useful when acting as a blocked, mono/interleaved delay line
int32_t PushZeros(uint32_t NumSamplesOfZeros) {
SampleType* DestBuffer = InternalBuffer.data();
const uint32_t ReadIndex = ReadCounter;
const uint32_t WriteIndex = WriteCounter;
int32_t NumToZeroEnd = std::min<int32_t>(NumSamplesOfZeros, Remainder());
const int32_t NumToZeroBegin = std::min<int32_t>(NumToZeroEnd, Capacity - WriteIndex);
memzero(&DestBuffer[WriteIndex], NumToZeroBegin * sizeof(SampleType));
memzero(&DestBuffer[0], (NumToZeroEnd - NumToZeroBegin) * sizeof(SampleType));
WriteCounter = (WriteIndex + NumToZeroEnd) % Capacity;
return NumToZeroEnd;
}
// Push a single sample onto this buffer.
// Returns false if the buffer is full.
bool Push(const SampleType& InElement) {
if (Remainder() == 0) {
return false;
} else {
SampleType* DestBuffer = InternalBuffer.data();
const uint32_t ReadIndex = ReadCounter;
const uint32_t WriteIndex = WriteCounter;
DestBuffer[WriteIndex] = InElement;
WriteCounter = (WriteIndex + 1) % Capacity;
return true;
}
}
bool Push(SampleType&& InElement) {
if (Remainder() == 0) {
return false;
} else {
SampleType* DestBuffer = InternalBuffer.data();
const uint32_t ReadIndex = ReadCounter;
const uint32_t WriteIndex = WriteCounter;
DestBuffer[WriteIndex] = MoveTemp(InElement);
WriteCounter = (WriteIndex + 1) % Capacity;
return true;
}
}
// Same as Pop(), but does not increment the read counter.
int32_t Peek(SampleType* OutBuffer, uint32_t NumSamples) const {
const SampleType* SrcBuffer = InternalBuffer.data();
const uint32_t ReadIndex = ReadCounter;
const uint32_t WriteIndex = WriteCounter;
int32_t NumToCopy = std::min<int32_t>(NumSamples, Num());
const int32_t NumRead = std::min<int32_t>(NumToCopy, Capacity - ReadIndex);
memcpy(OutBuffer, &SrcBuffer[ReadIndex], NumRead * sizeof(SampleType));
memcpy(&OutBuffer[NumRead], &SrcBuffer[0], (NumToCopy - NumRead) * sizeof(SampleType));
check(NumSamples < ((uint32_t)INT32_MAX));
return NumToCopy;
}
// Peeks a single element.
// returns false if the element is empty.
bool Peek(SampleType& OutElement) const {
if (Num() == 0) {
return false;
} else {
SampleType* SrcBuffer = InternalBuffer.data();
const uint32_t ReadIndex = ReadCounter;
OutElement = SrcBuffer[ReadIndex];
return true;
}
}
// Pops some amount of samples into this circular buffer.
// Returns the amount of samples read.
int32_t Pop(SampleType* OutBuffer, uint32_t NumSamples) {
int32_t NumSamplesRead = Peek(OutBuffer, NumSamples);
check(NumSamples < ((uint32_t)INT32_MAX));
ReadCounter = (ReadCounter + NumSamplesRead) % Capacity;
return NumSamplesRead;
}
// Pops some amount of samples into this circular buffer.
// Returns the amount of samples read.
int32_t Pop(uint32_t NumSamples) {
check(NumSamples < ((uint32_t)INT32_MAX));
int32_t NumSamplesRead = std::min<int32_t>(NumSamples, Num());
ReadCounter = (ReadCounter + NumSamplesRead) % Capacity;
return NumSamplesRead;
}
// Pops a single element.
// Will assert if the buffer is empty. Please check Num() > 0 before calling.
SampleType Pop() {
// Calling this when the buffer is empty is considered a fatal error.
check(Num() > 0);
SampleType* SrcBuffer = InternalBuffer.data();
const uint32_t ReadIndex = ReadCounter;
SampleType PoppedValue = MoveTempIfPossible(InternalBuffer[ReadIndex]);
ReadCounter = (ReadCounter + 1) % Capacity;
return PoppedValue;
}
// When called, seeks the read or write cursor to only retain either the NumSamples latest data
// (if bRetainOldestSamples is false) or the NumSamples oldest data (if bRetainOldestSamples is true)
// in the buffer. Cannot be used to increase the capacity of this buffer.
void SetNum(uint32_t NumSamples, bool bRetainOldestSamples = false) {
check(NumSamples < Capacity);
if (bRetainOldestSamples) {
WriteCounter = (ReadCounter + NumSamples) % Capacity;
} else {
int64_t ReadCounterNum = ((int32_t) WriteCounter) - ((int32_t) NumSamples);
if (ReadCounterNum < 0) {
ReadCounterNum = Capacity + ReadCounterNum;
}
ReadCounter = ReadCounterNum;
}
}
// Get number of samples that can be popped off of the buffer.
uint32_t Num() const {
const int32_t ReadIndex = ReadCounter;
const int32_t WriteIndex = WriteCounter;
if (WriteIndex >= ReadIndex) {
return WriteIndex - ReadIndex;
} else {
return Capacity - ReadIndex + WriteIndex;
}
}
// Get the current capacity of the buffer
uint32_t GetCapacity() const {
return Capacity;
}
// Get number of samples that can be pushed onto the buffer before it is full.
uint32_t Remainder() const {
const uint32_t ReadIndex = ReadCounter;
const uint32_t WriteIndex = WriteCounter;
return (Capacity - 1 - WriteIndex + ReadIndex) % Capacity;
}
};

View File

@ -0,0 +1,140 @@
#include "channel_interface.h"
#include "channel_node.h"
channel_interface::channel_interface(uint32_t input_channels, uint32_t output_channels) {
const uint32_t input_node_num = input_channels / 2;
const uint32_t output_node_num = output_channels / 2;
null_channel_node* null_node = null_channel_node::get();
input_headers_.resize(input_channels);
output_headers_.resize(output_channels);
for (int i = 0; i < input_node_num; ++i)
{
input_channel_nodes.push_back(null_node);
set_input_channel(i, null_node);
}
for (int i = 0; i < output_node_num; ++i)
{
output_channel_nodes.push_back(null_node);
set_output_channel(i, null_node);
}
}
void channel_interface::set_input_channel(uint32_t node_index, channel_node* node) {
if (node_index >= input_channel_nodes.size())
return;
input_channel_nodes[node_index] = node;
const auto& node_headers = node->get_channel_headers();
input_headers_[node_index * 2] = node_headers[0];
input_headers_[node_index * 2 + 1] = node_headers[1];
// TODO rebuild process node
}
void channel_interface::set_input_channel(const std::vector<channel_node*>& nodes, uint32_t start_index) {
const uint32_t input_num = std::min(nodes.size() - start_index, input_channel_nodes.size());
for (uint32_t i = 0; i < input_num; ++i)
{
set_input_channel(i + start_index, nodes[i]);
}
}
void channel_interface::set_output_channel(uint32_t node_index, channel_node* node) {
if (node_index >= output_channel_nodes.size())
return;
output_channel_nodes[node_index] = node;
const auto& node_headers = node->get_channel_headers();
output_headers_[node_index * 2] = node_headers[0];
output_headers_[node_index * 2 + 1] = node_headers[1];
// TODO rebuild process node
}
void channel_interface::set_output_channel(const std::vector<channel_node*>& nodes, uint32_t start_index) {
const uint32_t output_num = std::min(nodes.size() - start_index, output_channel_nodes.size());
for (uint32_t i = 0; i < output_num; ++i)
{
set_output_channel(i + start_index, nodes[i]);
}
}
uint32_t channel_interface::get_input_node_index(channel_node* node) {
for (int i = 0; i < input_channel_nodes.size(); ++i) {
if (input_channel_nodes[i] == node)
return i;
}
return -1;
}
uint32_t channel_interface::get_output_node_index(channel_node* node) {
for (int i = 0; i < output_channel_nodes.size(); ++i) {
if (output_channel_nodes[i] == node)
return i;
}
return -1;
}
void channel_interface::set_input_channel_node_name(uint32_t node_index, uint32_t channel_index,
const std::string& name) {
input_channel_names_[node_index][channel_index] = name;
}
void channel_interface::set_output_channel_node_name(uint32_t node_index, uint32_t channel_index,
const std::string& name) {
output_channel_names_[node_index][channel_index] = name;
}
std::map<uint32_t, std::string> channel_interface::get_input_channel_node_name(uint32_t node_index) {
return input_channel_names_[node_index];
}
std::map<uint32_t, std::string> channel_interface::get_input_channel_node_name(channel_node* node) {
uint32_t index = get_input_node_index(node);
if (index == -1)
return {};
return get_input_channel_node_name(index);
}
std::map<uint32_t, std::string> channel_interface::get_output_channel_node_name(uint32_t node_index) {
return output_channel_names_[node_index];
}
std::map<uint32_t, std::string> channel_interface::get_output_channel_node_name(channel_node* node) {
uint32_t index = get_output_node_index(node);
if (index == -1)
return {};
return get_output_channel_node_name(index);
}
void channel_interface::remove_track(mixer_track* track) {
for (int i = 0; i < input_channel_nodes.size(); ++i) {
if (input_channel_nodes[i]->type == channel_node_type::mixer) {
auto mixer_node = static_cast<mixer_channel_node*>(input_channel_nodes[i]);
if (mixer_node->get_track() == track) {
set_input_channel(i, null_channel_node::get());
}
}
}
for (int i = 0; i < output_channel_nodes.size(); ++i) {
if (output_channel_nodes[i]->type == channel_node_type::mixer) {
auto mixer_node = static_cast<mixer_channel_node*>(output_channel_nodes[i]);
if (mixer_node->get_track() == track) {
set_output_channel(i, null_channel_node::get());
}
}
}
}
mixer_channel_interface::mixer_channel_interface(mixer_track* track) : channel_interface(0, 2), track_(track) {
auto* node = new mixer_channel_node(this, track, 0);
set_output_channel(0, node);
}
mixer_channel_interface::~mixer_channel_interface() {
delete output_channel_nodes[0];
}

View File

@ -0,0 +1,49 @@
#pragma once
#include <map>
class mixer_track;
class channel_node;
class channel_interface {
public:
channel_interface(uint32_t input_channels, uint32_t output_channels);
virtual ~channel_interface() = default;
void set_input_channel(uint32_t node_index, channel_node* node);
void set_input_channel(const std::vector<channel_node*>& nodes, uint32_t start_index);
void set_output_channel(uint32_t node_index, channel_node* node);
void set_output_channel(const std::vector<channel_node*>& nodes, uint32_t start_index);
uint32_t get_input_node_index(channel_node* node);
uint32_t get_output_node_index(channel_node* node);
float** get_input_headers() { return input_headers_.data(); }
float** get_output_headers() { return output_headers_.data(); }
void set_input_channel_node_name(uint32_t node_index, uint32_t channel_index, const std::string& name);
void set_output_channel_node_name(uint32_t node_index, uint32_t channel_index, const std::string& name);
std::map<uint32_t, std::string> get_input_channel_node_name(uint32_t node_index);
std::map<uint32_t, std::string> get_input_channel_node_name(channel_node* node);
std::map<uint32_t, std::string> get_output_channel_node_name(uint32_t node_index);
std::map<uint32_t, std::string> get_output_channel_node_name(channel_node* node);
void remove_track(mixer_track* track);
std::vector<channel_node*> input_channel_nodes;
std::vector<channel_node*> output_channel_nodes;
private:
std::map<uint32_t, std::map<uint32_t, std::string>> input_channel_names_;
std::map<uint32_t, std::map<uint32_t, std::string>> output_channel_names_;
std::vector<float*> input_headers_;
std::vector<float*> output_headers_;
};
class mixer_channel_interface : public channel_interface {
public:
explicit mixer_channel_interface(mixer_track* track);
~mixer_channel_interface() override;
[[nodiscard]]mixer_track* get_track() const { return track_; }
private:
mixer_track* track_;
};

View File

@ -0,0 +1,17 @@
#include "channel_node.h"
#include "mixer.h"
#include "mixer_track.h"
null_channel_node null_channel_node::instance;
mixer_channel_node::mixer_channel_node(channel_interface* in_owner, mixer_track* in_track, uint32_t in_node_index): channel_node(in_owner, channel_node_type::mixer) {
const auto& headers = in_track->buffer.get_headers_vector();
channel_headers_.push_back(headers[in_node_index * 2]);
channel_headers_.push_back(headers[in_node_index * 2 + 1]);
}
const std::vector<float*>& null_channel_node::get_channel_headers() {
return g_mixer.zero_track->buffer.get_headers_vector();
}

View File

@ -0,0 +1,47 @@
#pragma once
class mixer_track;
class channel_interface;
enum class channel_node_type {
unknown,
mixer,
null,
};
class channel_node {
public:
virtual ~channel_node() = default;
channel_node(channel_interface* in_owner, channel_node_type in_type) : owner(in_owner), type(in_type) {}
virtual const std::vector<float*>& get_channel_headers() = 0;
virtual std::string get_name() = 0;
channel_interface* owner;
channel_node_type type;
};
class mixer_channel_node : public channel_node {
public:
mixer_channel_node(channel_interface* in_owner, mixer_track* in_track, uint32_t in_node_index);
const std::vector<float*>& get_channel_headers() override { return channel_headers_; }
std::string get_name() override { return "MixerChannelNode"; }
[[nodiscard]] mixer_track* get_track() const { return track_; }
private:
mixer_track* track_;
std::vector<float*> channel_headers_;
uint32_t node_index_;
};
class null_channel_node : public channel_node {
static null_channel_node instance;
public:
null_channel_node() : channel_node(nullptr, channel_node_type::null) {}
static null_channel_node* get() { return &instance; }
std::string get_name() override { return "NullChannelNode"; }
const std::vector<float*>& get_channel_headers() override;
};

208
core/audio/mixer/mixer.cpp Normal file
View File

@ -0,0 +1,208 @@
#include "mixer.h"
#include "channel_interface.h"
#include "channel_node.h"
#include "mixer_track.h"
#include "audio/plugin_host/plugin_host.h"
#include "audio/plugin_host/plugin_host_manager.h"
#include "thread_message/thread_message_hubs.h"
#include "taskflow/taskflow.hpp"
void build_effect_channel_interface(mixer_track* track, const channel_interface* interface, std::map<mixer_track*, int32_t>& processed_tracks) {
int32_t& track_current_layer = processed_tracks[track];
auto& input_channel_nodes = interface->input_channel_nodes;
auto& output_channel_nodes = interface->output_channel_nodes;
// 如果这个效果器需要从其他轨道输入,那么目标轨道的深度就是这个轨道的深度+1
for (int i = 1; i < input_channel_nodes.size(); ++i)
{
channel_node* node = input_channel_nodes[i];
if (node->type != channel_node_type::mixer)
continue;
const mixer_channel_node* mixer_node = static_cast<mixer_channel_node*>(node);
auto* InputTrack = mixer_node->get_track();
int32_t& target_mixer_current_layer = processed_tracks[InputTrack];
target_mixer_current_layer = std::max(target_mixer_current_layer + 1, track_current_layer + 1);
}
// 如果这个效果器需要输出到其他轨道,那么这个轨道的深度就是目标轨道的深度+1
for (int i = 1; i < output_channel_nodes.size(); ++i)
{
auto* node = output_channel_nodes[i];
if (node->type != channel_node_type::mixer)
continue;
const auto* MixerChannelNode = static_cast<mixer_channel_node*>(node);
auto* MixerTrack = MixerChannelNode->get_track();
const int32_t& TargetMixerCurrentLayer = processed_tracks[MixerTrack];
track_current_layer = std::max(track_current_layer, TargetMixerCurrentLayer + 1);
}
}
int32_t build_process_node_internal(mixer_track* track, std::map<mixer_track*, int32_t>& processed_tracks, int32_t layer) {
int32_t& track_current_layer = processed_tracks[track];
track_current_layer = std::max(track_current_layer, layer);
for (const mixer_track_link& child_link: track->children)
{
mixer_track* ChildTrack = child_link.track;
build_process_node_internal(ChildTrack, processed_tracks, layer + 1);
for (const plugin_host* effect: ChildTrack->effects)
{
build_effect_channel_interface(ChildTrack, effect->interface.get(), processed_tracks);
}
}
return ++layer;
}
void build_instrument_process_node(const plugin_host* host, std::map<mixer_track*, int32_t>& processed_tracks) {
for (mixer_track* Track : host->owner_tracks)
{
build_effect_channel_interface(Track, host->interface.get(), processed_tracks);
}
}
void mixer::init(singleton_initliazer& initliazer) {
singleton_t<mixer>::init(initliazer);
zero_track = new dummy_track();
zero_track->rename("zero");
zero_track->init();
const auto master = new dummy_track();
master->rename("master");
master->init();
tracks_.push_back(master);
}
void mixer::release() {
singleton_t<mixer>::release();
for (mixer_track* const track: tracks_) {
delete track;
}
delete zero_track;
}
dummy_track* mixer::create_dummy_track(const std::string& in_name) {
auto* track = new dummy_track();
track->rename(in_name);
track->init();
mixer_track_link link{};
link.track = track;
link.send_level = 1.0f;
get_master()->children.push_back(link);
g_audio_thread_hub.push_message([track, this]() {
thread_register_track(track);
});
return track;
}
instrument_track* mixer::create_instrument_track(const std::string& in_name) {
auto* track = new instrument_track();
track->rename(in_name);
track->init();
mixer_track_link link{};
link.track = track;
link.send_level = 1.0f;
get_master()->children.push_back(link);
// register track
g_audio_thread_hub.push_message([track, this] {
thread_register_track(track);
});
return track;
}
void mixer::remove_track(mixer_track* track) {
g_audio_thread_hub.push_message([track, this] {
thread_register_track(track);
});
}
void mixer::process(uint32_t in_frames) {
tf::Executor executor;
tf::Taskflow taskflow;
tf::Task previous_task = taskflow.emplace([] {});
for (const auto& order: layer_order_) {
for (const auto& layer = layer_tracks_[order]; const auto& track: layer) {
taskflow.emplace([track, in_frames] {
track->process(in_frames);
}).succeed(previous_task);
}
tf::Task new_layer = taskflow.emplace([] {});
new_layer.succeed(previous_task);
previous_task = new_layer;
}
executor.run(taskflow).wait();
post_process(in_frames);
}
void mixer::reset() {
for (const auto track: tracks_) {
track->buffer.clear();
}
}
void mixer::build_process_node() {
if (tracks_.empty())
return;
std::map<mixer_track*, int32_t> processed_tracks;
auto* master = get_master();
build_process_node_internal(master, processed_tracks, 0);
const auto& instruments = g_plugin_host_manager.get_plugin_hosts();
for (const plugin_host* instrument: instruments) {
build_instrument_process_node(instrument, processed_tracks);
}
layer_tracks_.clear();
for (const auto& pair: processed_tracks) {
layer_tracks_[pair.second].push_back(pair.first);
}
layer_order_.clear();
for (const auto& key: layer_tracks_ | std::views::keys) {
layer_order_.push_back(key);
}
std::ranges::sort(layer_order_);
}
void mixer::request_build_process_node() {
g_audio_thread_hub.push_message([this] {
build_process_node();
});
}
void post_process_internal(mixer_track* track, uint32_t in_frames, std::vector<mixer_track*>& processed_tracks) {
if (std::ranges::find(processed_tracks, track) != processed_tracks.end())
return;
for (const auto& link: track->children) {
post_process_internal(link.track, in_frames, processed_tracks);
track->buffer.mix(link.track->buffer, link.send_level);
}
// CalculatePeak ?
processed_tracks.push_back(track);
}
void mixer::post_process(uint32_t in_frames) const {
const auto master = get_master();
std::vector<mixer_track*> processed_tracks;
post_process_internal(master, in_frames, processed_tracks);
}
void mixer::thread_register_track(mixer_track* track) {
tracks_.push_back(track);
build_process_node();
}
void mixer::thread_remove_track(mixer_track* track) {
if (const auto it = std::ranges::find(tracks_, track); it != tracks_.end()) {
tracks_.erase(it);
}
get_master()->remove_child(track);
build_process_node();
on_remove_track.broadcast(track);
g_main_thread_hub.push_message([track, this]() {
on_remove_track_main_thread.broadcast(track);
delete track;
});
}

45
core/audio/mixer/mixer.h Normal file
View File

@ -0,0 +1,45 @@
#pragma once
#include "misc/delegates.h"
#include "misc/singleton/singleton.h"
#include <map>
class channel_interface;
class instrument_track;
class mixer_track;
class dummy_track;
class mixer : public singleton_t<mixer> {
typedef std::pair<int32_t, std::vector<mixer_track*>> layer_type;
public:
void init(singleton_initliazer& initliazer) override;
void release() override;
const char* get_name() override { return "mixer"; }
dummy_track* create_dummy_track(const std::string& in_name);
instrument_track* create_instrument_track(const std::string& in_name);
void remove_track(mixer_track* track);
void process(uint32_t in_frames);
void reset();
[[nodiscard]] dummy_track* get_master() const { return reinterpret_cast<dummy_track*>(tracks_[0]); }
void build_process_node();
void request_build_process_node();
dummy_track* zero_track; // 用于没有任何音频输出的通道
multicast_delegate<mixer_track*> on_remove_track;
multicast_delegate<mixer_track*> on_remove_track_main_thread;
private:
void post_process(uint32_t in_frames) const;
void thread_register_track(mixer_track* track);
void thread_remove_track(mixer_track* track);
std::vector<mixer_track*> tracks_;
std::map<int32_t, std::vector<mixer_track*>> layer_tracks_;
std::vector<int32_t> layer_order_;
};
CORE_API inline mixer g_mixer;

View File

@ -0,0 +1,45 @@
#include "mixer_track.h"
#include "audio/plugin_host/plugin_host.h"
mixer_track::~mixer_track() {
}
void mixer_track::init() {
}
void mixer_track::add_effect(plugin_host* in_effect) {
in_effect->owner_tracks.push_back(this);
}
void mixer_track::remove_effect(plugin_host* in_effect) {
}
void mixer_track::add_child(mixer_track* in_child, float in_send_level) {
children.push_back({in_child, in_send_level});
}
void mixer_track::remove_child(mixer_track* in_child) {
const auto new_end = std::remove_if(children.begin(), children.end(), [in_child](const mixer_track_link& link) {
return link.track == in_child;
});
children.erase(new_end, children.end());
}
void mixer_track::process(uint32_t in_frames) {
for (auto effect : effects)
effect->process(in_frames);
buffer.multiple(volume);
}
void instrument_track::rename(const std::string& in_name) {
instrument_->name = in_name;
}
std::string instrument_track::get_name() const {
return instrument_->name;
}

View File

@ -0,0 +1,67 @@
#pragma once
#include "audio/misc/audio_buffer.h"
class mixer_track;
class plugin_host;
enum class mixer_track_type {
unknown,
dummy,
instrument,
};
struct mixer_track_link {
mixer_track* track;
float send_level;
};
class mixer_track {
public:
explicit mixer_track(mixer_track_type in_type) : type_(in_type) {}
virtual ~mixer_track();
void init();
void add_effect(plugin_host* in_effect);
void remove_effect(plugin_host* in_effect);
void add_child(mixer_track* in_child, float in_send_level);
void remove_child(mixer_track* in_child);
void process(uint32_t in_frames);
virtual void rename(const std::string& in_name) = 0;
[[nodiscard]] virtual std::string get_name() const = 0;
[[nodiscard]] float** get_headers() { return buffer.get_headers(); }
[[nodiscard]] mixer_track_type get_type() const { return type_; }
audio_buffer buffer;
std::atomic<float> volume = 1.0f;
std::vector<plugin_host*> effects{};
std::vector<mixer_track_link> children{};
private:
void add_effect_internal(plugin_host* in_effect);
const mixer_track_type type_;
};
class instrument_track : public mixer_track {
public:
instrument_track() : mixer_track(mixer_track_type::instrument) {}
void rename(const std::string& in_name) override;
[[nodiscard]] std::string get_name() const override;
[[nodiscard]] plugin_host* get_instrument() const { return instrument_; }
private:
plugin_host* instrument_;
};
class dummy_track : public mixer_track {
public:
dummy_track() : mixer_track(mixer_track_type::dummy) {}
void rename(const std::string& in_name) override { name_ = in_name; }
[[nodiscard]] std::string get_name() const override { return name_.c_str(); }
private:
std::string name_;
};

View File

@ -0,0 +1,4 @@
#include "midi_sequencer.h"
void midi_sequencer::process(double sample_rate, uint32_t in_frames) {
}

View File

@ -0,0 +1,11 @@
#pragma once
#include "misc/singleton/singleton.h"
class midi_sequencer : public singleton_t<midi_sequencer> {
public:
void process(double sample_rate, uint32_t in_frames);
const char* get_name() override { return "midi_sequencer"; }
};
CORE_API inline midi_sequencer g_midi_sequencer;

View File

@ -0,0 +1 @@
#include "plugin_host.h"

View File

@ -0,0 +1,33 @@
#pragma once
#include "imgui.h"
#include "GLFW/glfw3.h"
class mixer_track;
class channel_interface;
class plugin_host {
public:
virtual bool load_plugin(const char* path) = 0;
virtual void set_enabled(bool enabled) = 0;
virtual bool is_enabled() const = 0;
virtual void on_update_sample_rate(double sample_rate) = 0;
virtual void on_update_buffer_size(int buffer_size) = 0;
virtual uint32_t get_input_channels() const = 0;
virtual uint32_t get_output_channels() const = 0;
virtual void update_channel_node_name() {}
virtual void process(uint32_t frame_num) {}
virtual void open_editor(GLFWwindow* window) {}
virtual void close_editor() {}
virtual void idle_editor() {}
virtual bool has_editor() const { return false; }
virtual ImVec2 get_editor_size() const { return ImVec2(0, 0); }
std::string name;
std::string vendor;
std::shared_ptr<channel_interface> interface;
std::vector<mixer_track*> owner_tracks;
};

View File

@ -0,0 +1,31 @@
#include "plugin_host_manager.h"
#include "taskflow/taskflow.hpp"
#include "plugin_host.h"
#include "audio/mixer/channel_interface.h"
#include "audio/mixer/mixer.h"
#include "misc/singleton/singleton_manager.h"
void plugin_host_manager::init(singleton_initliazer& initliazer) {
singleton_t<plugin_host_manager>::init(initliazer);
auto* mixer_ptr = initliazer.require<mixer>();
mixer_ptr->on_remove_track.add_raw(this, &plugin_host_manager::on_mixer_track_removed);
}
void plugin_host_manager::process(uint32_t in_frames) const {
tf::Executor executor;
tf::Taskflow taskflow;
for (auto host : plugin_hosts_) {
taskflow.emplace([host, in_frames] {
host->process(in_frames);
});
}
executor.run(taskflow).wait();
}
void plugin_host_manager::on_mixer_track_removed(mixer_track* track) {
for (auto host : plugin_hosts_) {
host->interface->remove_track(track);
}
}

View File

@ -0,0 +1,22 @@
#pragma once
#include "misc/singleton/singleton.h"
class plugin_host;
class mixer_track;
class plugin_host_manager : public singleton_t<plugin_host_manager> {
public:
void init(singleton_initliazer& initliazer) override;
const std::vector<plugin_host*>& get_plugin_hosts() { return plugin_hosts_; }
void process(uint32_t in_frames) const;
const char* get_name() override { return "plugin_host_manager"; }
private:
void on_mixer_track_removed(mixer_track* track);
std::vector<plugin_host*> plugin_hosts_{};
};
CORE_API inline plugin_host_manager g_plugin_host_manager;

View File

@ -7,3 +7,12 @@
#define CORE_API __declspec(dllimport)
#endif
#if defined(_DEBUG) || defined(DEBUG)
#define checkf(expr, msg, ...) if (!(expr)) { spdlog::error(msg, __VA_ARGS__); throw std::runtime_error(fmt::format(msg, __VA_ARGS__)); }
#define check(expr) if (!(expr)) { spdlog::error("Check failed: {}", #expr); throw std::runtime_error(fmt::format("Check failed: {}", #expr)); }
#else
#define checkf(...)
#define check(...)
#endif

3
core/misc/delegates.cpp Normal file
View File

@ -0,0 +1,3 @@
#include "delegates.h"
unsigned int delegate_handle::CURRENT_ID = 0;

1103
core/misc/delegates.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,43 @@
#pragma once
#include "singleton_manager.h"
class singleton_initliazer;
template<class T>
class CORE_API singleton_impl {
public:
singleton_impl() {
instance_ = static_cast<T*>(this);
}
static void set(T* instance) {
instance_ = instance;
}
static T* get() {
return instance_;
}
static bool exists() {
return instance_ != nullptr;
}
protected:
static inline T* instance_ = nullptr;
};
class CORE_API singleton {
public:
virtual ~singleton() = default;
virtual void init(singleton_initliazer& initliazer) {}
virtual void post_init() {}
virtual void begin_release() {}
virtual void release() {}
virtual const char* get_name() = 0;
};
template<class T>
class CORE_API singleton_t : public singleton, public singleton_impl<T> {
public:
singleton_t() {
singleton_manager::get()->add(this);
}
};

View File

@ -0,0 +1,27 @@
#include "singleton_manager.h"
#include "singleton.h"
void singleton_manager::add(singleton* s) {
singletons_.push_back(s);
}
void singleton_manager::init() const {
singleton_initliazer initliazer;
for (const auto s : singletons_) {
s->init(initliazer);
initliazer.singletons_.push_back(s);
}
for (const auto s : singletons_) {
s->post_init();
}
}
void singleton_manager::release() const {
for (const auto s : singletons_) {
s->begin_release();
}
for (const auto s : singletons_) {
s->release();
}
}

View File

@ -0,0 +1,35 @@
#pragma once
class singleton;
class singleton_initliazer {
public:
template<class T>
T* require() {
auto instance = T::get();
if (std::find(singletons_.begin(), singletons_.end(), instance) != singletons_.end()) {
return instance;
}
singletons_.push_back(instance);
instance->init(*this);
return instance;
}
std::vector<singleton*> singletons_{};
};
class CORE_API singleton_manager {
public:
static singleton_manager* get() {
static singleton_manager instance;
return &instance;
}
void add(singleton* s);
void init() const;
void release() const;
private:
std::vector<singleton*> singletons_;
};

View File

@ -1,7 +1,6 @@
#include "renderer.h"
#include "imgui_impl_glfw.h"
#include "render_target.h"
#include "application/application.h"
#include "texture.h"
@ -463,9 +462,3 @@ std::shared_ptr<texture> renderer::create_texture(const uint8_t* data, int width
out->upload(data, width * height * 4);
return out;
}
std::shared_ptr<render_target> renderer::create_render_target(int width, int height, vk::Format format) {
auto out = std::make_shared<render_target>();
out->init(width, height, format);
return out;
}

View File

@ -11,7 +11,8 @@
#define APP_USE_VULKAN_DEBUG_REPORT
#endif
class pixel_shader_drawer;
#define APP_USE_UNLIMITED_FRAME_RATE
class render_target;
class texture;
constexpr ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f);
@ -31,7 +32,7 @@ static void check_vk_result(VkResult err) {
if (err == VK_SUCCESS)
return;
if (err < 0) {
spdlog::error("[vulkan] Error: VkResult = {}", err);
spdlog::error("[vulkan] Error: VkResult = {}", (int)err);
abort();
}
}
@ -51,7 +52,6 @@ public:
void end_frame(GLFWwindow* window_handle);
static CORE_API std::shared_ptr<texture> create_texture(const uint8_t* data, int width, int height, vk::Format format);
static CORE_API std::shared_ptr<render_target> create_render_target(int width, int height, vk::Format format);
void set_vsync(const bool vsync) { vsync_ = vsync; }

View File

@ -0,0 +1 @@
#include "thread_message.h"

View File

@ -0,0 +1,19 @@
#pragma once
class CORE_API thread_message {
public:
virtual ~thread_message() = default;
virtual void execute() = 0;
};
class CORE_API lamba_thread_message : public thread_message {
public:
lamba_thread_message(const std::function<void()>& in_func) : function(in_func) {
}
void execute() override {
function();
}
std::function<void()> function;
};

View File

@ -0,0 +1,12 @@
#include "thread_message_hub.h"
#include "thread_message.h"
void thread_message_hub::process_messages() {
while (!messages_.empty()) {
thread_message* message = messages_.front();
message->execute();
mem_pool_.free(message);
messages_.pop();
}
}

View File

@ -0,0 +1,31 @@
#pragma once
#include <queue>
#include "mempool.h"
#include "thread_message.h"
class CORE_API thread_message_hub {
public:
template<typename T, typename ...Args>
void push_message(Args&&... args) {
T* message = mem_pool_.alloc<T>(std::forward<Args>(args)...);
push_message(message);
}
template<typename T>
void push_message() {
T* message = mem_pool_.alloc<T>();
push_message(message);
}
void push_message(thread_message* message) {
messages_.push(message);
}
void push_message(const std::function<void()>& func) {
lamba_thread_message* message = mem_pool_.alloc<lamba_thread_message>(func);
push_message(message);
}
void process_messages();
private:
std::queue<thread_message*> messages_;
mempool mem_pool_ = mempool(1024000);
};

View File

@ -0,0 +1,7 @@
#pragma once
#include "thread_message_hub.h"
inline thread_message_hub g_main_thread_hub;
inline thread_message_hub g_audio_thread_hub;
#define PUSH_MAIN_THREAD(code) g_main_thread_hub.push_message(code);

19
third_party/mempool/CMakeLists.txt vendored Normal file
View File

@ -0,0 +1,19 @@
cmake_minimum_required(VERSION 3.5)
project(mempool)
set(CMAKE_C_STANDARD 99)
set(CMAKE_CPP_STANDARD 23)
add_library(${PROJECT_NAME} SHARED
mempool/ncx_core.h
mempool/ncx_lock.h
mempool/ncx_slab.c
mempool/ncx_slab.h
mempool/unistd.h
mempool.h
extern.h
)
include_directories(mempool)
target_include_directories(${PROJECT_NAME} PUBLIC mempool ${CMAKE_CURRENT_SOURCE_DIR})
target_precompile_headers(${PROJECT_NAME} PUBLIC extern.h)
add_definitions(-Dmempool_EXPORTS)

7
third_party/mempool/extern.h vendored Normal file
View File

@ -0,0 +1,7 @@
#pragma once
#ifdef mempool_EXPORTS
#define MEMPOOL_API __declspec(dllexport)
#else
#define MEMPOOL_API __declspec(dllimport)
#endif

46
third_party/mempool/mempool.h vendored Normal file
View File

@ -0,0 +1,46 @@
#pragma once
#include "ncx_slab.h"
class mempool {
public:
mempool(size_t pool_size = 1024000) {
const auto space = (u_char*)malloc(pool_size);
mem_pool_ = (ncx_slab_pool_t*)space;
mem_pool_->addr = space;
mem_pool_->min_shift = 3;
mem_pool_->end = space + pool_size;
ncx_slab_init(mem_pool_);
}
~mempool() {
free(mem_pool_->addr);
}
[[nodiscard]] void* alloc(size_t size) const {
return ncx_slab_alloc(mem_pool_, size);
}
template<typename T, typename ...Args>
[[nodiscard]] T* alloc(Args&&... args) const {
T* out = (T*)alloc(sizeof(T));
new (out) T(std::forward<Args>(args)...);
return out;
}
template<typename T>
[[nodiscard]] T* alloc() const {
T* out = (T*)alloc(sizeof(T));
new (out) T();
return out;
}
void free(void* p) const {
ncx_slab_free(mem_pool_, p);
}
[[nodiscard]] ncx_slab_stat_t stat() const {
ncx_slab_stat_t out;
ncx_slab_stat(mem_pool_, &out);
return out;
}
private:
ncx_slab_pool_t* mem_pool_ = nullptr;
};

41
third_party/mempool/mempool/Makefile vendored Normal file
View File

@ -0,0 +1,41 @@
CC=gcc
CXX=g++
INC+=
LIB+=
#判断系统架构(32bit, 64bit)
ARCH := $(shell getconf LONG_BIT)
ifeq ($(ARCH),64)
CFLAGS+= -DNCX_PTR_SIZE=8
else
CFLAGS+= -DNCX_PTR_SIZE=4
endif
#CFLAGS+= -pipe -O0 -Wall -g3 -ggdb3
CFLAGS+= -pipe -O3
#定义是否打印日志
CFLAGS+= -DLOG_LEVEL=4
#是否与malloc类似模拟脏数据
#CFLAGS+= -DNCX_DEBUG_MALLOC
#是否自动合并碎片
CFLAGS+= -DPAGE_MERGE
TARGET=pool_test
ALL:$(TARGET)
OBJ= ncx_slab.o
$(TARGET):$(OBJ) main.o
$(CC) $(CFLAGS) -o $@ $^ $(LIB)
pool_bench:$(OBJ) bench.o
$(CC) $(CFLAGS) -o $@ $^ $(LIB)
%.o: %.c
$(CC) $(CFLAGS) $(INC) -c -o $@ $<
clean:
rm -f *.o
rm -f $(TARGET) pool_bench
install:

106
third_party/mempool/mempool/README.md vendored Normal file
View File

@ -0,0 +1,106 @@
ncx_mempool
======================
Description
===========
a nginx-based memory pool . <br/>
both share-memory and private-memory are supported. it is more efficient than malloc.<br/>
it is a very light weight and do not rely on any third-party library.
Examples
========
1 #include "ncx_slab.h"
2
3 int main(int argc, char **argv)
4 {
5 char *p;
6 size_t pool_size = 4096000; //4M
7 ncx_slab_stat_t stat;
8 u_char *space;
9 space = (u_char *)malloc(pool_size);
10
11 ncx_slab_pool_t *sp;
12 sp = (ncx_slab_pool_t*) space;
13
14 sp->addr = space;
15 sp->min_shift = 3;
16 sp->end = space + pool_size;
17
18 /*
19 * init
20 */
21 ncx_slab_init(sp);
22
23 /*
24 * alloc
25 */
26 p = ncx_slab_alloc(sp, 128);
27
28 /*
29 * memory usage api.
30 */
31 ncx_slab_stat(sp, &stat);
32
33 /*
34 * free
35 */
36 ncx_slab_free(sp, p);
37
38 free(space);
39
40 return 0;
41}
API
===
**ncx_slab_init(ncx_slab_pool_t *pool)** <br/>
**Description**: 初始化内存池结构;
**ncx_slab_alloc(ncx_slab_pool_t *pool, size_t size)**<br/>
**Description**: 内存分配
**ncx_slab_free(ncx_slab_pool_t *pool, void *p)** <br/>
**Description**: 释放内存
**ncx_slab_stat(ncx_slab_pool_t *pool, ncx_slab_stat_t *stat)**<br/>
**Description**: 查看内存池使用情况
Customization
=============
正如example所示内存池内存是由应用层先分配ncx_mempool是在给定的内存基础上进行分配和回收管理。 <br/>
所以内存池本身不关心所管理的内存是私有内存 or 共享内存;
ncx_lock.h 是锁接口;根据实际需要重定义: <br/>
1.多线程共享内存池可参考pthread_spin_lock <br/>
2.多进程共享内存池可参考nginx的ngx_shmtx.c实现spin lock <br/>
3.单进程单线程使用内存池,无锁编程..
ncx_log.h 是日志接口,根据实际需要重定义.
ncx_slab.c.orz 是 ncx_slab.c的详细注释方便理解.
Author
======
dcshi(施俊伟) <dcshi@qq.com>
Copyright and License
=====================
This module is licensed under the BSD license.
Copyright (C) 2013, by dcshi(施俊伟). <dcshi@qq.com>
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

79
third_party/mempool/mempool/bench.c vendored Normal file
View File

@ -0,0 +1,79 @@
#include "ncx_slab.h"
#include <sys/time.h>
uint64_t usTime()
{
struct timeval tv;
uint64_t usec;
gettimeofday(&tv, NULL);
usec = ((uint64_t)tv.tv_sec)*1000000LL;
usec += tv.tv_usec;
return usec;
}
int main(int argc, char **argv)
{
ncx_slab_pool_t *sp;
size_t pool_size;
u_char *space;
pool_size = 4096000; //4M
space = (u_char *)malloc(pool_size);
sp = (ncx_slab_pool_t*) space;
sp->addr = space;
sp->min_shift = 3;
sp->end = space + pool_size;
ncx_slab_init(sp);
char *p;
uint64_t us_begin;
uint64_t us_end;
size_t size[] = { 30, 120, 256, 500, 1000, 2000, 3000, 5000};
printf("size\tncx\tmalloc\tpct\n");
int i, j;
uint64_t t1, t2;
for (j = 0; j < sizeof(size)/sizeof(size_t); j++)
{
size_t s = size[j];
printf("%d\t", s);
//test for ncx_pool
us_begin = usTime();
for(i = 0; i < 1000000; i++)
{
p = ncx_slab_alloc(sp, s);
ncx_slab_free(sp, p);
}
us_end = usTime();
t1 = (us_end - us_begin);
printf("%llu \t", t1/1000);
// test for malloc
us_begin = usTime();
for(i = 0; i < 1000000; i++)
{
p = (char*)malloc(s);
free(p);
}
us_end = usTime();
t2 = (us_end - us_begin);
printf("%llu\t", t2/1000);
printf("%.2f\n", (double)t1 / (double)t2);
}
free(space);
return 0;
}

53
third_party/mempool/mempool/main.c vendored Normal file
View File

@ -0,0 +1,53 @@
#include "ncx_slab.h"
int main(int argc, char **argv)
{
char *p;
size_t pool_size = 4096000; //4M
ncx_slab_stat_t stat;
u_char *space;
space = (u_char *)malloc(pool_size);
ncx_slab_pool_t *sp;
sp = (ncx_slab_pool_t*) space;
sp->addr = space;
sp->min_shift = 3;
sp->end = space + pool_size;
ncx_slab_init(sp);
int i;
for (i = 0; i < 1000000; i++)
{
p = ncx_slab_alloc(sp, 128 + i);
if (p == NULL)
{
printf("%d\n", i);
return -1;
}
ncx_slab_free(sp, p);
}
ncx_slab_stat(sp, &stat);
printf("##########################################################################\n");
for (i = 0; i < 2500; i++)
{
p = ncx_slab_alloc(sp, 30 + i);
if (p == NULL)
{
printf("%d\n", i);
return -1;
}
if (i % 3 == 0)
{
ncx_slab_free(sp, p);
}
}
ncx_slab_stat(sp, &stat);
free(space);
return 0;
}

25
third_party/mempool/mempool/ncx_core.h vendored Normal file
View File

@ -0,0 +1,25 @@
#ifndef _NCX_CORE_H_
#define _NCX_CORE_H_
#include <stdlib.h>
#include <stdio.h>
#include <inttypes.h>
#include <string.h>
#include <stdbool.h>
typedef unsigned char u_char;
typedef uintptr_t ncx_uint_t;
typedef intptr_t ncx_int_t;
#ifndef NCX_ALIGNMENT
#define NCX_ALIGNMENT sizeof(unsigned long) /* platform word */
#endif
#define ncx_align(d, a) (((d) + (a - 1)) & ~(a - 1))
#define ncx_align_ptr(p, a) \
(u_char *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & ~((uintptr_t) a - 1))
#define ncx_memzero(buf, n) (void) memset(buf, 0, n)
#define ncx_memset(buf, c, n) (void) memset(buf, c, n)
#endif

13
third_party/mempool/mempool/ncx_lock.h vendored Normal file
View File

@ -0,0 +1,13 @@
#ifndef _NCX_LOCK_H_
#define _NCX_LOCK_H_
#define ncx_shmtx_lock(x) { /*void*/ }
#define ncx_shmtx_unlock(x) { /*void*/ }
typedef struct {
ncx_uint_t spin;
} ncx_shmtx_t;
#endif

831
third_party/mempool/mempool/ncx_slab.c vendored Normal file
View File

@ -0,0 +1,831 @@
#include "ncx_slab.h"
#include <unistd.h>
#define NCX_SLAB_PAGE_MASK 3
#define NCX_SLAB_PAGE 0
#define NCX_SLAB_BIG 1
#define NCX_SLAB_EXACT 2
#define NCX_SLAB_SMALL 3
#if (NCX_PTR_SIZE == 4)
#define NCX_SLAB_PAGE_FREE 0
#define NCX_SLAB_PAGE_BUSY 0xffffffff
#define NCX_SLAB_PAGE_START 0x80000000
#define NCX_SLAB_SHIFT_MASK 0x0000000f
#define NCX_SLAB_MAP_MASK 0xffff0000
#define NCX_SLAB_MAP_SHIFT 16
#define NCX_SLAB_BUSY 0xffffffff
#else /* (NCX_PTR_SIZE == 8) */
#define NCX_SLAB_PAGE_FREE 0
#define NCX_SLAB_PAGE_BUSY 0xffffffffffffffff
#define NCX_SLAB_PAGE_START 0x8000000000000000
#define NCX_SLAB_SHIFT_MASK 0x000000000000000f
#define NCX_SLAB_MAP_MASK 0xffffffff00000000
#define NCX_SLAB_MAP_SHIFT 32
#define NCX_SLAB_BUSY 0xffffffffffffffff
#endif
#if (NCX_DEBUG_MALLOC)
#define ncx_slab_junk(p, size) ncx_memset(p, 0xA5, size)
#else
#define ncx_slab_junk(p, size)
#endif
static ncx_slab_page_t* ncx_slab_alloc_pages(ncx_slab_pool_t* pool,
ncx_uint_t pages);
static void ncx_slab_free_pages(ncx_slab_pool_t* pool, ncx_slab_page_t* page,
ncx_uint_t pages);
static bool ncx_slab_empty(ncx_slab_pool_t* pool, ncx_slab_page_t* page);
static ncx_uint_t ncx_slab_max_size;
static ncx_uint_t ncx_slab_exact_size;
static ncx_uint_t ncx_slab_exact_shift;
static ncx_uint_t ncx_pagesize;
static ncx_uint_t ncx_pagesize_shift;
static ncx_uint_t ncx_real_pages;
void ncx_slab_init(ncx_slab_pool_t* pool) {
u_char* p;
size_t size;
ncx_uint_t i, n, pages;
ncx_slab_page_t* slots;
/*pagesize*/
ncx_pagesize = getpagesize();
for (n = ncx_pagesize, ncx_pagesize_shift = 0;
n >>= 1; ncx_pagesize_shift++) {
/* void */
}
/* STUB */
if (ncx_slab_max_size == 0) {
ncx_slab_max_size = ncx_pagesize / 2;
ncx_slab_exact_size = ncx_pagesize / (8 * sizeof(uintptr_t));
for (n = ncx_slab_exact_size; n >>= 1; ncx_slab_exact_shift++) {
/* void */
}
}
pool->min_size = 1 << pool->min_shift;
p = (u_char*) pool + sizeof(ncx_slab_pool_t);
slots = (ncx_slab_page_t*) p;
n = ncx_pagesize_shift - pool->min_shift;
for (i = 0; i < n; i++) {
slots[i].slab = 0;
slots[i].next = &slots[i];
slots[i].prev = 0;
}
p += n * sizeof(ncx_slab_page_t);
size = pool->end - p;
ncx_slab_junk(p, size);
pages = (ncx_uint_t) (size / (ncx_pagesize + sizeof(ncx_slab_page_t)));
ncx_memzero(p, pages * sizeof(ncx_slab_page_t));
pool->pages = (ncx_slab_page_t*) p;
pool->free.prev = 0;
pool->free.next = (ncx_slab_page_t*) p;
pool->pages->slab = pages;
pool->pages->next = &pool->free;
pool->pages->prev = (uintptr_t) &pool->free;
pool->start = (u_char*)
ncx_align_ptr((uintptr_t) p + pages * sizeof(ncx_slab_page_t),
ncx_pagesize);
ncx_real_pages = (pool->end - pool->start) / ncx_pagesize;
pool->pages->slab = ncx_real_pages;
}
void* ncx_slab_alloc(ncx_slab_pool_t* pool, size_t size) {
ncx_shmtx_lock(&pool->mutex);
void* p = ncx_slab_alloc_locked(pool, size);
ncx_shmtx_unlock(&pool->mutex);
return p;
}
void* ncx_slab_alloc_locked(ncx_slab_pool_t* pool, size_t size) {
size_t s;
uintptr_t p, n, m, mask,* bitmap;
ncx_uint_t i, slot, shift, map;
ncx_slab_page_t* page,* prev,* slots;
if (size >= ncx_slab_max_size) {
// debug("slab alloc: %zu", size);
page = ncx_slab_alloc_pages(pool, (size >> ncx_pagesize_shift)
+ ((size % ncx_pagesize) ? 1 : 0));
if (page) {
p = (page - pool->pages) << ncx_pagesize_shift;
p += (uintptr_t) pool->start;
} else {
p = 0;
}
goto done;
}
if (size > pool->min_size) {
shift = 1;
for (s = size - 1; s >>= 1; shift++) {
/* void */
}
slot = shift - pool->min_shift;
} else {
size = pool->min_size;
shift = pool->min_shift;
slot = 0;
}
slots = (ncx_slab_page_t*) ((u_char*) pool + sizeof(ncx_slab_pool_t));
page = slots[slot].next;
if (page->next != page) {
if (shift < ncx_slab_exact_shift) {
do {
p = (page - pool->pages) << ncx_pagesize_shift;
bitmap = (uintptr_t*) (pool->start + p);
map = (1 << (ncx_pagesize_shift - shift))
/ (sizeof(uintptr_t) * 8);
for (n = 0; n < map; n++) {
if (bitmap[n] != NCX_SLAB_BUSY) {
for (m = 1, i = 0; m; m <<= 1, i++) {
if ((bitmap[n] & m)) {
continue;
}
bitmap[n] |= m;
i = ((n * sizeof(uintptr_t) * 8) << shift)
+ (i << shift);
if (bitmap[n] == NCX_SLAB_BUSY) {
for (n = n + 1; n < map; n++) {
if (bitmap[n] != NCX_SLAB_BUSY) {
p = (uintptr_t) bitmap + i;
goto done;
}
}
prev = (ncx_slab_page_t*)
(page->prev & ~NCX_SLAB_PAGE_MASK);
prev->next = page->next;
page->next->prev = page->prev;
page->next = NULL;
page->prev = NCX_SLAB_SMALL;
}
p = (uintptr_t) bitmap + i;
goto done;
}
}
}
page = page->next;
} while (page);
} else if (shift == ncx_slab_exact_shift) {
do {
if (page->slab != NCX_SLAB_BUSY) {
for (m = 1, i = 0; m; m <<= 1, i++) {
if ((page->slab & m)) {
continue;
}
page->slab |= m;
if (page->slab == NCX_SLAB_BUSY) {
prev = (ncx_slab_page_t*)
(page->prev & ~NCX_SLAB_PAGE_MASK);
prev->next = page->next;
page->next->prev = page->prev;
page->next = NULL;
page->prev = NCX_SLAB_EXACT;
}
p = (page - pool->pages) << ncx_pagesize_shift;
p += i << shift;
p += (uintptr_t) pool->start;
goto done;
}
}
page = page->next;
} while (page);
} else {
/* shift > ncx_slab_exact_shift */
n = ncx_pagesize_shift - (page->slab & NCX_SLAB_SHIFT_MASK);
n = 1 << n;
n = ((uintptr_t) 1 << n) - 1;
mask = n << NCX_SLAB_MAP_SHIFT;
do {
if ((page->slab & NCX_SLAB_MAP_MASK) != mask) {
for (m = (uintptr_t) 1 << NCX_SLAB_MAP_SHIFT, i = 0;
m & mask;
m <<= 1, i++) {
if ((page->slab & m)) {
continue;
}
page->slab |= m;
if ((page->slab & NCX_SLAB_MAP_MASK) == mask) {
prev = (ncx_slab_page_t*)
(page->prev & ~NCX_SLAB_PAGE_MASK);
prev->next = page->next;
page->next->prev = page->prev;
page->next = NULL;
page->prev = NCX_SLAB_BIG;
}
p = (page - pool->pages) << ncx_pagesize_shift;
p += i << shift;
p += (uintptr_t) pool->start;
goto done;
}
}
page = page->next;
} while (page);
}
}
page = ncx_slab_alloc_pages(pool, 1);
if (page) {
if (shift < ncx_slab_exact_shift) {
p = (page - pool->pages) << ncx_pagesize_shift;
bitmap = (uintptr_t*) (pool->start + p);
s = 1 << shift;
n = (1 << (ncx_pagesize_shift - shift)) / 8 / s;
if (n == 0) {
n = 1;
}
bitmap[0] = (2 << n) - 1;
map = (1 << (ncx_pagesize_shift - shift)) / (sizeof(uintptr_t) * 8);
for (i = 1; i < map; i++) {
bitmap[i] = 0;
}
page->slab = shift;
page->next = &slots[slot];
page->prev = (uintptr_t) &slots[slot] | NCX_SLAB_SMALL;
slots[slot].next = page;
p = ((page - pool->pages) << ncx_pagesize_shift) + s * n;
p += (uintptr_t) pool->start;
goto done;
} else if (shift == ncx_slab_exact_shift) {
page->slab = 1;
page->next = &slots[slot];
page->prev = (uintptr_t) &slots[slot] | NCX_SLAB_EXACT;
slots[slot].next = page;
p = (page - pool->pages) << ncx_pagesize_shift;
p += (uintptr_t) pool->start;
goto done;
} else {
/* shift > ncx_slab_exact_shift */
page->slab = ((uintptr_t) 1 << NCX_SLAB_MAP_SHIFT) | shift;
page->next = &slots[slot];
page->prev = (uintptr_t) &slots[slot] | NCX_SLAB_BIG;
slots[slot].next = page;
p = (page - pool->pages) << ncx_pagesize_shift;
p += (uintptr_t) pool->start;
goto done;
}
}
p = 0;
done:
// debug("slab alloc: %p", (void *)p);
return (void*) p;
}
void ncx_slab_free(ncx_slab_pool_t* pool, void* p) {
ncx_shmtx_lock(&pool->mutex);
ncx_slab_free_locked(pool, p);
ncx_shmtx_unlock(&pool->mutex);
}
void ncx_slab_free_locked(ncx_slab_pool_t* pool, void* p) {
size_t size;
uintptr_t slab, m,* bitmap;
ncx_uint_t n, type, slot, shift, map;
ncx_slab_page_t* slots,* page;
// debug("slab free: %p", p);
if ((u_char*) p < pool->start || (u_char*) p > pool->end) {
// error("ncx_slab_free(): outside of pool");
goto fail;
}
n = ((u_char*) p - pool->start) >> ncx_pagesize_shift;
page = &pool->pages[n];
slab = page->slab;
type = page->prev & NCX_SLAB_PAGE_MASK;
switch (type) {
case NCX_SLAB_SMALL:
shift = slab & NCX_SLAB_SHIFT_MASK;
size = 1 << shift;
if ((uintptr_t) p & (size - 1)) {
goto wrong_chunk;
}
n = ((uintptr_t) p & (ncx_pagesize - 1)) >> shift;
m = (uintptr_t) 1 << (n & (sizeof(uintptr_t) * 8 - 1));
n /= (sizeof(uintptr_t) * 8);
bitmap = (uintptr_t*) ((uintptr_t) p & ~(ncx_pagesize - 1));
if (bitmap[n] & m) {
if (page->next == NULL) {
slots = (ncx_slab_page_t*)
((u_char*) pool + sizeof(ncx_slab_pool_t));
slot = shift - pool->min_shift;
page->next = slots[slot].next;
slots[slot].next = page;
page->prev = (uintptr_t) &slots[slot] | NCX_SLAB_SMALL;
page->next->prev = (uintptr_t) page | NCX_SLAB_SMALL;
}
bitmap[n] &= ~m;
n = (1 << (ncx_pagesize_shift - shift)) / 8 / (1 << shift);
if (n == 0) {
n = 1;
}
if (bitmap[0] & ~(((uintptr_t) 1 << n) - 1)) {
goto done;
}
map = (1 << (ncx_pagesize_shift - shift)) / (sizeof(uintptr_t) * 8);
for (n = 1; n < map; n++) {
if (bitmap[n]) {
goto done;
}
}
ncx_slab_free_pages(pool, page, 1);
goto done;
}
goto chunk_already_free;
case NCX_SLAB_EXACT:
m = (uintptr_t) 1 <<
(((uintptr_t) p & (ncx_pagesize - 1)) >> ncx_slab_exact_shift);
size = ncx_slab_exact_size;
if ((uintptr_t) p & (size - 1)) {
goto wrong_chunk;
}
if (slab & m) {
if (slab == NCX_SLAB_BUSY) {
slots = (ncx_slab_page_t*)
((u_char*) pool + sizeof(ncx_slab_pool_t));
slot = ncx_slab_exact_shift - pool->min_shift;
page->next = slots[slot].next;
slots[slot].next = page;
page->prev = (uintptr_t) &slots[slot] | NCX_SLAB_EXACT;
page->next->prev = (uintptr_t) page | NCX_SLAB_EXACT;
}
page->slab &= ~m;
if (page->slab) {
goto done;
}
ncx_slab_free_pages(pool, page, 1);
goto done;
}
goto chunk_already_free;
case NCX_SLAB_BIG:
shift = slab & NCX_SLAB_SHIFT_MASK;
size = 1 << shift;
if ((uintptr_t) p & (size - 1)) {
goto wrong_chunk;
}
m = (uintptr_t) 1 << ((((uintptr_t) p & (ncx_pagesize - 1)) >> shift)
+ NCX_SLAB_MAP_SHIFT);
if (slab & m) {
if (page->next == NULL) {
slots = (ncx_slab_page_t*)
((u_char*) pool + sizeof(ncx_slab_pool_t));
slot = shift - pool->min_shift;
page->next = slots[slot].next;
slots[slot].next = page;
page->prev = (uintptr_t) &slots[slot] | NCX_SLAB_BIG;
page->next->prev = (uintptr_t) page | NCX_SLAB_BIG;
}
page->slab &= ~m;
if (page->slab & NCX_SLAB_MAP_MASK) {
goto done;
}
ncx_slab_free_pages(pool, page, 1);
goto done;
}
goto chunk_already_free;
case NCX_SLAB_PAGE:
if ((uintptr_t) p & (ncx_pagesize - 1)) {
goto wrong_chunk;
}
if (slab == NCX_SLAB_PAGE_FREE) {
// alert("ncx_slab_free(): page is already free");
goto fail;
}
if (slab == NCX_SLAB_PAGE_BUSY) {
// alert("ncx_slab_free(): pointer to wrong page");
goto fail;
}
n = ((u_char*) p - pool->start) >> ncx_pagesize_shift;
size = slab & ~NCX_SLAB_PAGE_START;
ncx_slab_free_pages(pool, &pool->pages[n], size);
ncx_slab_junk(p, size << ncx_pagesize_shift);
return;
}
/* not reached */
return;
done:
ncx_slab_junk(p, size);
return;
wrong_chunk:
// error("ncx_slab_free(): pointer to wrong chunk");
goto fail;
chunk_already_free:
// error("ncx_slab_free(): chunk is already free");
fail:
return;
}
static ncx_slab_page_t* ncx_slab_alloc_pages(ncx_slab_pool_t* pool, ncx_uint_t pages) {
ncx_slab_page_t* page,* p;
for (page = pool->free.next; page != &pool->free; page = page->next) {
if (page->slab >= pages) {
if (page->slab > pages) {
page[pages].slab = page->slab - pages;
page[pages].next = page->next;
page[pages].prev = page->prev;
p = (ncx_slab_page_t*) page->prev;
p->next = &page[pages];
page->next->prev = (uintptr_t) &page[pages];
} else {
p = (ncx_slab_page_t*) page->prev;
p->next = page->next;
page->next->prev = page->prev;
}
page->slab = pages | NCX_SLAB_PAGE_START;
page->next = NULL;
page->prev = NCX_SLAB_PAGE;
if (--pages == 0) {
return page;
}
for (p = page + 1; pages; pages--) {
p->slab = NCX_SLAB_PAGE_BUSY;
p->next = NULL;
p->prev = NCX_SLAB_PAGE;
p++;
}
return page;
}
}
// error("ncx_slab_alloc() failed: no memory");
return NULL;
}
static void ncx_slab_free_pages(ncx_slab_pool_t* pool, ncx_slab_page_t* page,
ncx_uint_t pages) {
ncx_slab_page_t* prev,* next;
if (pages > 1) {
ncx_memzero(&page[1], (pages - 1)* sizeof(ncx_slab_page_t));
}
if (page->next) {
prev = (ncx_slab_page_t*) (page->prev & ~NCX_SLAB_PAGE_MASK);
prev->next = page->next;
page->next->prev = page->prev;
}
page->slab = pages;
page->prev = (uintptr_t) &pool->free;
page->next = pool->free.next;
page->next->prev = (uintptr_t) page;
pool->free.next = page;
#ifdef PAGE_MERGE
if (pool->pages != page) {
prev = page - 1;
if (ncx_slab_empty(pool, prev)) {
for (; prev >= pool->pages; prev--) {
if (prev->slab != 0)
{
pool->free.next = page->next;
page->next->prev = (uintptr_t) &pool->free;
prev->slab += pages;
ncx_memzero(page, sizeof(ncx_slab_page_t));
page = prev;
break;
}
}
}
}
if ((page - pool->pages + page->slab) < ncx_real_pages) {
next = page + page->slab;
if (ncx_slab_empty(pool, next))
{
prev = (ncx_slab_page_t *) (next->prev);
prev->next = next->next;
next->next->prev = next->prev;
page->slab += next->slab;
ncx_memzero(next, sizeof(ncx_slab_page_t));
}
}
#endif
}
void ncx_slab_dummy_init(ncx_slab_pool_t* pool) {
ncx_uint_t n;
ncx_pagesize = getpagesize();
for (n = ncx_pagesize, ncx_pagesize_shift = 0;
n >>= 1; ncx_pagesize_shift++) {
/* void */
}
if (ncx_slab_max_size == 0) {
ncx_slab_max_size = ncx_pagesize / 2;
ncx_slab_exact_size = ncx_pagesize / (8 * sizeof(uintptr_t));
for (n = ncx_slab_exact_size; n >>= 1; ncx_slab_exact_shift++) {
/* void */
}
}
}
void ncx_slab_stat(ncx_slab_pool_t* pool, ncx_slab_stat_t* stat) {
uintptr_t m, n, mask, slab;
uintptr_t* bitmap;
ncx_uint_t i, j, map, type, obj_size;
ncx_slab_page_t* page;
ncx_memzero(stat, sizeof(ncx_slab_stat_t));
page = pool->pages;
stat->pages = (pool->end - pool->start) / ncx_pagesize;;
for (i = 0; i < stat->pages; i++) {
slab = page->slab;
type = page->prev & NCX_SLAB_PAGE_MASK;
switch (type) {
case NCX_SLAB_SMALL:
n = (page - pool->pages) << ncx_pagesize_shift;
bitmap = (uintptr_t*) (pool->start + n);
obj_size = 1 << slab;
map = (1 << (ncx_pagesize_shift - slab))
/ (sizeof(uintptr_t) * 8);
for (j = 0; j < map; j++) {
for (m = 1; m; m <<= 1) {
if ((bitmap[j] & m)) {
stat->used_size += obj_size;
stat->b_small += obj_size;
}
}
}
stat->p_small++;
break;
case NCX_SLAB_EXACT:
if (slab == NCX_SLAB_BUSY) {
stat->used_size += sizeof(uintptr_t) * 8 * ncx_slab_exact_size;
stat->b_exact += sizeof(uintptr_t) * 8 * ncx_slab_exact_size;
} else {
for (m = 1; m; m <<= 1) {
if (slab & m) {
stat->used_size += ncx_slab_exact_size;
stat->b_exact += ncx_slab_exact_size;
}
}
}
stat->p_exact++;
break;
case NCX_SLAB_BIG:
j = ncx_pagesize_shift - (slab & NCX_SLAB_SHIFT_MASK);
j = 1 << j;
j = ((uintptr_t) 1 << j) - 1;
mask = j << NCX_SLAB_MAP_SHIFT;
obj_size = 1 << (slab & NCX_SLAB_SHIFT_MASK);
for (m = (uintptr_t) 1 << NCX_SLAB_MAP_SHIFT; m & mask; m <<= 1) {
if ((page->slab & m)) {
stat->used_size += obj_size;
stat->b_big += obj_size;
}
}
stat->p_big++;
break;
case NCX_SLAB_PAGE:
if (page->prev == NCX_SLAB_PAGE) {
slab = slab & ~NCX_SLAB_PAGE_START;
stat->used_size += slab * ncx_pagesize;
stat->b_page += slab * ncx_pagesize;
stat->p_page += slab;
i += (slab - 1);
break;
}
default:
if (slab > stat->max_free_pages) {
stat->max_free_pages = page->slab;
}
stat->free_page += slab;
i += (slab - 1);
break;
}
page = pool->pages + i + 1;
}
stat->pool_size = pool->end - pool->start;
stat->used_pct = stat->used_size * 100 / stat->pool_size;
// info("pool_size : %zu bytes", stat->pool_size);
// info("used_size : %zu bytes", stat->used_size);
// info("used_pct : %zu%%\n", stat->used_pct);
// info("total page count : %zu", stat->pages);
// info("free page count : %zu\n", stat->free_page);
// info("small slab use page : %zu,\tbytes : %zu", stat->p_small, stat->b_small);
// info("exact slab use page : %zu,\tbytes : %zu", stat->p_exact, stat->b_exact);
// info("big slab use page : %zu,\tbytes : %zu", stat->p_big, stat->b_big);
// info("page slab use page : %zu,\tbytes : %zu\n", stat->p_page, stat->b_page);
// info("max free pages : %zu\n", stat->max_free_pages);
}
static bool ncx_slab_empty(ncx_slab_pool_t* pool, ncx_slab_page_t* page) {
ncx_slab_page_t* prev;
if (page->slab == 0) {
return true;
}
//page->prev == PAGE | SMALL | EXACT | BIG
if (page->next == NULL) {
return false;
}
prev = (ncx_slab_page_t*) (page->prev & ~NCX_SLAB_PAGE_MASK);
while (prev >= pool->pages) {
prev = (ncx_slab_page_t*) (prev->prev & ~NCX_SLAB_PAGE_MASK);
};
if (prev == &pool->free) {
return true;
}
return false;
}

1133
third_party/mempool/mempool/ncx_slab.c.orz vendored Normal file

File diff suppressed because it is too large Load Diff

56
third_party/mempool/mempool/ncx_slab.h vendored Normal file
View File

@ -0,0 +1,56 @@
#pragma once
#include "ncx_core.h"
#include "ncx_lock.h"
#include "extern.h"
typedef struct ncx_slab_page_s ncx_slab_page_t;
struct ncx_slab_page_s {
uintptr_t slab;
ncx_slab_page_t *next;
uintptr_t prev;
};
typedef struct {
size_t min_size;
size_t min_shift;
ncx_slab_page_t *pages;
ncx_slab_page_t free;
u_char *start;
u_char *end;
ncx_shmtx_t mutex;
void *addr;
} ncx_slab_pool_t;
typedef struct {
size_t pool_size, used_size, used_pct;
size_t pages, free_page;
size_t p_small, p_exact, p_big, p_page; /* 四种slab占用的page数 */
size_t b_small, b_exact, b_big, b_page; /* 四种slab占用的byte数 */
size_t max_free_pages; /* 最大的连续可用page数 */
} ncx_slab_stat_t;
#ifdef __cplusplus
extern "C" { // only need to export C interface if
// used by C++ source code
#endif
MEMPOOL_API void ncx_slab_init(ncx_slab_pool_t *pool);
MEMPOOL_API void* ncx_slab_alloc(ncx_slab_pool_t *pool, size_t size);
MEMPOOL_API void* ncx_slab_alloc_locked(ncx_slab_pool_t *pool, size_t size);
MEMPOOL_API void ncx_slab_free(ncx_slab_pool_t *pool, void *p);
MEMPOOL_API void ncx_slab_free_locked(ncx_slab_pool_t *pool, void *p);
MEMPOOL_API void ncx_slab_dummy_init(ncx_slab_pool_t *pool);
MEMPOOL_API void ncx_slab_stat(ncx_slab_pool_t *pool, ncx_slab_stat_t *stat);
#ifdef __cplusplus
}
#endif

12
third_party/mempool/mempool/unistd.h vendored Normal file
View File

@ -0,0 +1,12 @@
#pragma once
#ifdef WIN32
#include <windows.h>
inline uintptr_t getpagesize() {
SYSTEM_INFO systemInfo;
GetSystemInfo(&systemInfo);
return systemInfo.dwPageSize;
}
#else
#include <unisid.h>
#endif

2
third_party/spdlog vendored

@ -1 +1 @@
Subproject commit 696db97f672e9082e50e50af315d0f4234c82397
Subproject commit 69d412b5260ffdb67b2f18a60f4eb1043035d1cf

1
third_party/taskflow vendored Submodule

@ -0,0 +1 @@
Subproject commit a8434d095ff60c53583646e80b6c57c688ee2a4f