ADD: Async Sound Loading + AudioLayer

This commit is contained in:
sebastian 2026-08-04 18:00:33 +02:00
parent a5a97d141e
commit a1936d666c
20 changed files with 285 additions and 47 deletions

View File

@ -198,6 +198,8 @@ add_library(Engine STATIC
engine/src/core/audio/SoundBuffer.h
engine/src/core/audio/SoundSource.cpp
engine/src/core/audio/SoundSource.h
engine/src/loader/sounds/SoundUploader.cpp
engine/src/loader/sounds/SoundUploader.h
)
target_include_directories(Engine PUBLIC engine/src ${dr_libs_SOURCE_DIR})
target_link_libraries(Engine PUBLIC OpenGL::GL glfw glad glm::glm imgui spdlog::spdlog tinyobjloader stb_image OpenAL::OpenAL)
@ -252,6 +254,8 @@ add_executable(ColorRace
game/src/ludo/LudoAiController.h
game/src/ludo/ecs/highlight/HighlightSystem.cpp
game/src/ludo/ecs/highlight/HighlightSystem.h
game/src/AudioLayer.cpp
game/src/AudioLayer.h
)

View File

@ -10,6 +10,7 @@
#include "EngineConfig.h"
#include "Window.h"
#include "audio/AudioDevice.h"
#include "inputsOutputs/context/InputContextStack.h"
#include "inputsOutputs/inputs/Keyboard.h"
#include "inputsOutputs/inputs/Mouse.h"
@ -41,6 +42,7 @@ namespace engine {
Mouse m_mouse{*m_window};
SceneManager m_sceneManager{m_inputContextStack};
ImGuiContext m_imguiContext{*m_window}; // neu
engine::audio::AudioDevice m_audioDevice;
};

View File

@ -10,28 +10,22 @@
#include "AudioDevice.h"
namespace engine::audio {
SoundBuffer::SoundBuffer(const std::string &path) {
unsigned int channels, sampleRate;
drwav_uint64 frameCount;
drwav_int16* pcmData = drwav_open_file_and_read_pcm_frames_s16(
path.c_str(), &channels, &sampleRate, &frameCount, nullptr
);
if (!pcmData) {
throw std::runtime_error("Failed to load sound file: " + path);
}
ALenum format = (channels == 1) ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;
drwav_uint64 dataSize = frameCount * channels * sizeof(drwav_int16);
alGenBuffers(1, &m_bufferID);
alBufferData(m_bufferID, format, pcmData, dataSize, sampleRate);
checkAlError("alBufferData");
drwav_free(pcmData, nullptr);
}
SoundBuffer::~SoundBuffer() {
if (m_bufferID) alDeleteBuffers(1, &m_bufferID);
}
SoundBuffer::SoundBuffer(SoundBuffer &&other) noexcept : m_bufferID(other.m_bufferID) {
other.m_bufferID = 0;
}
SoundBuffer & SoundBuffer::operator=(SoundBuffer &&other) noexcept {
if (this != &other) {
release();
m_bufferID = other.m_bufferID;
other.m_bufferID = 0;
}
return *this;
}
} // engine

View File

@ -11,17 +11,28 @@
namespace engine::audio {
class SoundBuffer {
public:
explicit SoundBuffer(const std::string& path);
explicit SoundBuffer(ALuint bufferID) : m_bufferID(bufferID) {}
~SoundBuffer();
SoundBuffer(const SoundBuffer&) = delete;
SoundBuffer& operator=(const SoundBuffer&) = delete;
SoundBuffer(SoundBuffer&& other) noexcept;
SoundBuffer& operator=(SoundBuffer&& other) noexcept;
ALuint id() const {
return m_bufferID;
}
private:
ALuint m_bufferID = 0;
void release() {
if (m_bufferID != 0) {
alDeleteBuffers(1, &m_bufferID);
m_bufferID = 0;
}
}
};
} // engine

View File

@ -23,6 +23,24 @@ namespace engine::audio {
SoundSource(const SoundSource&) = delete;
SoundSource& operator=(const SoundSource&) = delete;
SoundSource(SoundSource&& other) noexcept
: m_source(other.m_source)
{
other.m_source = 0;
}
SoundSource& operator=(SoundSource&& other) noexcept
{
if (this != &other) {
release();
m_source = other.m_source;
other.m_source = 0;
}
return *this;
}
void setBuffer(const SoundBuffer& buffer) {
alSourcei(m_source, AL_BUFFER, static_cast<ALint>(buffer.id()));
checkAlError("alSourcei(AL_BUFFER)");
@ -50,6 +68,14 @@ namespace engine::audio {
}
private:
ALuint m_source = 0;
void release()
{
if (m_source != 0) {
alDeleteSources(1, &m_source);
m_source = 0;
}
}
};
inline void updateListener(float px, float py, float pz,

View File

@ -4,10 +4,12 @@
#include "AssetLoader.h"
#include <dr_wav.h>
#include <iostream>
#include "models/ModelImporter.h"
#include "models/ModelUploader.h"
#include "sounds/SoundUploader.h"
#include "spdlog/spdlog.h"
#include "textures/TextureImporter.h"
#include "textures/TextureUploader.h"
@ -25,6 +27,8 @@ void engine::AssetLoader::scheduleAsset(const AssetRequest &request) {
spdlog::debug("Scheduled Texture Request: {} -> {}", req.name, req.path);
} else if constexpr (std::is_same_v<Type, ModelRequest>) {
spdlog::debug("Scheduled Model Request: {} -> {}", req.name, req.path);
} else if constexpr (std::is_same_v<Type, SoundRequest>) {
spdlog::debug("Scheduled Sound Request: {} -> {}", req.name, req.path);
}
}, request);
}
@ -62,6 +66,9 @@ std::vector<engine::LoadedAsset> engine::AssetLoader::processUploadQueue(int max
std::cout << "Moddel Assets" << modelAssets.size() << std::endl;
result.insert(result.end(), modelAssets.begin(), modelAssets.end());
std::cout << "Result size" << result.size() << std::endl;
} else if constexpr (std::is_same_v<T, RawSoundData>) {
auto soundAsset = SoundUploader::upload(rawData);
result.emplace_back(LoadedSound{rawData.name, std::make_shared<audio::SoundBuffer>(std::move(soundAsset))});
} else {
static_assert(
always_false<T>,
@ -104,6 +111,10 @@ void engine::AssetLoader::loadingThreadFunc() {
return processTextureRequest(req);
} else if constexpr (std::is_same_v<T, ModelRequest>) {
return processModelRequest(req);
} else if constexpr (std::is_same_v<T, SoundRequest>) {
return processSoundRequest(req);
} else {
static_assert(always_false<T>, "Unhandled asset type in AssetLoader::loadingThreadFunc");
}
}, request);
@ -128,6 +139,35 @@ engine::RawModelBundle engine::AssetLoader::processModelRequest(const ModelReque
return modelData;
}
engine::RawSoundData engine::AssetLoader::processSoundRequest(const SoundRequest &request) {
RawSoundData soundData;
unsigned int channels, sampleRate;
drwav_uint64 frameCount;
drwav_int16* pcmData = drwav_open_file_and_read_pcm_frames_s16(
request.path.c_str(), &channels, &sampleRate, &frameCount, nullptr
);
if (!pcmData) {
throw std::runtime_error("Failed to load sound file: " + request.path);
}
soundData.name = request.name;
soundData.channels = channels;
soundData.sampleRate = sampleRate;
soundData.bitsPerSample = 16;
size_t dataSize = frameCount * channels * sizeof(drwav_int16);
soundData.pcmData.resize(dataSize);
std::memcpy(
soundData.pcmData.data(),
pcmData,
dataSize
);
drwav_free(pcmData, nullptr);
return soundData;
}
std::vector<engine::IntermediateAsset> engine::AssetLoader::determineUploadQueue(int maxPerFrame) {
std::vector<IntermediateAsset> result;
std::lock_guard lock(readyMutex);

View File

@ -46,6 +46,7 @@ inline constexpr bool always_false = false;
static RawTextureData processTextureRequest(const TextureRequest& request);
static RawModelBundle processModelRequest(const ModelRequest& request);
static RawSoundData processSoundRequest(const SoundRequest& request);
std::vector<IntermediateAsset> determineUploadQueue(int maxPerFrame);
std::queue<AssetRequest> pendingQueue;

View File

@ -28,6 +28,8 @@ void engine::AssetManager::insertAsset(LoadedAsset loadedAsset) {
insertModel(loaded.name, loaded.model);
} else if constexpr (std::is_same_v<T, LoadedTexture>) {
insertTexture(loaded.name, loaded.texture);
} else if constexpr (std::is_same_v<T, LoadedSound>) {
insertSound(loaded.name, loaded.sound);
}
}, loadedAsset);
}
@ -61,3 +63,24 @@ void engine::AssetManager::insertTexture(const std::string &name, std::shared_pt
void engine::AssetManager::unloadTexture(const std::string &name) {
m_textures.erase(name);
}
std::shared_ptr<engine::audio::SoundBuffer> engine::AssetManager::getSound(const std::string &name) const {
auto it = m_loadedAssets.find(name);
if (it == m_loadedAssets.end()) {
throw std::runtime_error("Sound not found: " + name);
}
return it->second;
}
bool engine::AssetManager::hasSound(const std::string &name) const {
return m_loadedAssets.contains(name);
}
void engine::AssetManager::insertSound(const std::string &name, std::shared_ptr<audio::SoundBuffer> sound) {
std::cout << "Inserting sound " << name << std::endl;
m_loadedAssets[name] = std::move(sound);
}
void engine::AssetManager::unloadSound(const std::string &name) {
m_loadedAssets.erase(name);
}

View File

@ -29,10 +29,16 @@ namespace engine {
void insertTexture(const std::string& name, std::shared_ptr<Texture2D> texture);
void unloadTexture(const std::string& name);
std::shared_ptr<audio::SoundBuffer> getSound(const std::string& name) const;
bool hasSound(const std::string& name) const;
void insertSound(const std::string& name, std::shared_ptr<audio::SoundBuffer> sound);
void unloadSound(const std::string& name);
private:
std::unordered_map<std::string, std::shared_ptr<Texture2D>> m_textures;
std::unordered_map<std::string, std::shared_ptr<Model>> m_models;
std::unordered_map<std::string, std::shared_ptr<audio::SoundBuffer>> m_loadedAssets;
};
}

View File

@ -8,6 +8,9 @@
#include <format>
#include <type_traits>
#include "RawAssetData.h"
#include "loader/AssetLoader.h"
namespace engine {
@ -27,14 +30,18 @@ namespace engine {
req.name,
req.path
);
}
else
} else if constexpr(std::is_same_v<Type, ModelRequest>)
{
return std::format(
"ModelRequest{{name='{}', path='{}'}}",
req.name,
req.path
);
} else if constexpr(std::is_same_v<Type, SoundRequest>){
return std::format(
"SoundRequest{{name='{}', path='{}'}}",
req.name,
req.path);
}
},
request);

View File

@ -28,11 +28,17 @@ namespace engine {
ModelSplitPolicy splitPolicy = ModelSplitPolicy::Combined;
};
struct SoundRequest {
std::string name;
std::string path;
};
using AssetRequest =
std::variant<
TextureRequest,
ModelRequest
ModelRequest,
SoundRequest
>;

View File

@ -9,6 +9,8 @@
#include <memory>
#include <string>
#include <variant>
#include "core/audio/SoundBuffer.h"
#include "loader/textures/Texture2D.h"
#include "loader/models/Model.h"
@ -24,7 +26,12 @@ namespace engine {
std::shared_ptr<Model> model;
};
using LoadedAsset = std::variant<LoadedTexture, LoadedModel>;
struct LoadedSound {
std::string name;
std::shared_ptr<audio::SoundBuffer> sound;
};
using LoadedAsset = std::variant<LoadedTexture, LoadedModel, LoadedSound>;
}
#endif //COLORRACE_LOADEDASSETS_H

View File

@ -76,11 +76,20 @@ namespace engine {
std::vector<RawTextureData> textures;
};
struct RawSoundData {
std::string name;
std::vector<std::byte> pcmData;
int channels;
int sampleRate;
int bitsPerSample;
};
using IntermediateAsset =
std::variant<
RawTextureData,
RawModelBundle
RawModelBundle,
RawSoundData
>;

View File

@ -0,0 +1,41 @@
//
// Created by sebastian on 04.08.26.
//
#include "SoundUploader.h"
#include <dr_wav.h>
#include "core/audio/AudioDevice.h"
engine::audio::SoundBuffer engine::SoundUploader::upload(const RawSoundData &data) {
auto* context = alcGetCurrentContext();
if (!context) {
std::cerr << "NO OPENAL CONTEXT IN UPLOAD THREAD\n";
}
ALenum format;
if (data.channels == 1) {
format = AL_FORMAT_MONO16;
} else if (data.channels == 2) {
format = AL_FORMAT_STEREO16;
} else {
throw std::runtime_error("Unsupported channel count");
}
ALuint bufferID = 0;
alGenBuffers(1, &bufferID);
alBufferData(
bufferID,
format,
data.pcmData.data(),
static_cast<ALsizei>(data.pcmData.size()),
data.sampleRate
);
audio::checkAlError("alBufferData");
return audio::SoundBuffer(bufferID);
}

View File

@ -0,0 +1,19 @@
//
// Created by sebastian on 04.08.26.
//
#ifndef COLORRACE_SOUNDUPLOADER_H
#define COLORRACE_SOUNDUPLOADER_H
#include "loader/assets/LoadedAssets.h"
#include "loader/assets/RawAssetData.h"
namespace engine {
class SoundUploader {
public:
static audio::SoundBuffer upload(const RawSoundData& data);
};
}
#endif //COLORRACE_SOUNDUPLOADER_H

28
game/src/AudioLayer.cpp Normal file
View File

@ -0,0 +1,28 @@
//
// Created by sebastian on 04.08.26.
//
#include "AudioLayer.h"
#include "GLFW/glfw3.h"
void ludo::AudioLayer::onUpdate(float deltaTime) {
Layer::onUpdate(deltaTime);
if (getKeyboard().keyPressEvent(GLFW_KEY_P)) {
for (auto& soundSource : soundSources) {
soundSource.play();
}
}
}
void ludo::AudioLayer::onAttachImpl() {
Layer::onAttachImpl();
auto rollingDiceSoundBufffer = m_assetManager.getSound("rolling_dice");
auto rollingDiceSoundSource = engine::audio::SoundSource();
rollingDiceSoundSource.setBuffer(*rollingDiceSoundBufffer);
rollingDiceSoundSource.setPosition(0.0f, 0.0f, 0.0f);
rollingDiceSoundSource.setGain(1.0f);
soundSources.push_back(std::move(rollingDiceSoundSource));
}

29
game/src/AudioLayer.h Normal file
View File

@ -0,0 +1,29 @@
//
// Created by sebastian on 04.08.26.
//
#ifndef COLORRACE_AUDIOLAYER_H
#define COLORRACE_AUDIOLAYER_H
#include "core/audio/SoundSource.h"
#include "layer/Layer.h"
#include "loader/assets/AssetManager.h"
namespace ludo {
class AudioLayer :public engine::Layer {
public:
AudioLayer(engine::AssetManager& assetManager, engine::Keyboard &keyboard, engine::Mouse &mouse): Layer(keyboard, mouse), m_assetManager(assetManager) {
}
void onUpdate(float deltaTime) override;
protected:
void onAttachImpl() override;
private:
engine::AssetManager& m_assetManager;
std::vector<engine::audio::SoundSource> soundSources;
};
}
#endif //COLORRACE_AUDIOLAYER_H

View File

@ -26,12 +26,6 @@ void GameLayer::onAttachImpl() {
m_cameraController = std::make_unique<engine::CameraController>(getInputStack(), getInputContext());
m_piecePresenter.spawnPieces(m_gameMode->getState());
try {
m_soundBuffer = std::make_unique<audio::SoundBuffer>("assets/sounds/rolling_dice.wav");
} catch (const std::exception& e) {
std::cerr <<"Audiofehler: " << e.what() << std::endl;
}
}
void GameLayer::onDetachImpl() {
@ -87,18 +81,6 @@ void GameLayer::onUpdate(float deltaTime) {
m_gameMode->sendCommand(ludo::MovePieceCommand{m_localPlayer, *pieceIndex});
}
}
if (getKeyboard().keyPressEvent(GLFW_KEY_P)) {
audio::SoundSource source;
source.setBuffer(*m_soundBuffer);
source.setPosition(0.0f, 0.0f, 0.0f);
source.setGain(1.0f);
source.play();
while (source.isPlaying()) {
}
}
}
void GameLayer::onRender() {

View File

@ -59,7 +59,7 @@ private:
std::vector<ludo::LudoAiController> aiControllers;
engine::audio::AudioDevice m_audioDevice;
std::unique_ptr<engine::audio::SoundBuffer> m_soundBuffer;

View File

@ -4,6 +4,7 @@
#include "GameScene.h"
#include "AudioLayer.h"
#include "GameLayer.h"
#include "core/inputsOutputs/inputs/Mouse.h"
#include "ecs/standardComponents/MeshComponent.h"
@ -38,6 +39,7 @@ void GameScene::onEnter() {
pieceModels["Green"] = assetManager->getModel("pawn_green");
pieceModels["Yellow"] = assetManager->getModel("pawn_yellow");
addLayer(std::make_unique<ludo::AudioLayer>(*assetManager, m_keyboard, m_mouse));
addLayer(std::make_unique<engine::DebugLayer>(*m_camera, entityRegistry, m_keyboard, m_mouse, engine::SphereFactory::createUVSphere(1.0f)));
addLayer(std::make_unique<GameLayer>(m_gameMode, entityRegistry, m_camera, m_pointLight, m_keyboard, m_mouse, assetManager->getModel("gameboard"), pieceModels));
@ -54,5 +56,6 @@ std::vector<engine::AssetRequest> GameScene::getRequiredAssets() const {
requests.emplace_back(engine::ModelRequest{"pawn_green", "assets/models/chess_green.obj", engine::ModelSplitPolicy::Combined});
requests.emplace_back(engine::ModelRequest{"pawn_yellow", "assets/models/chess_yellow.obj", engine::ModelSplitPolicy::Combined});
requests.emplace_back(engine::ModelRequest{ "gameboard", "assets/models/GameBoard.obj", engine::ModelSplitPolicy::Combined});
requests.emplace_back(engine::SoundRequest{ "rolling_dice", "assets/sounds/rolling_dice.wav"});
return requests;
}