Skip to content

Commit 9513597

Browse files
committed
audio fully works, GPU HRTF works as well as CPU HRTF.
1 parent 0833166 commit 9513597

File tree

8 files changed

+807
-329
lines changed

8 files changed

+807
-329
lines changed

attachments/simple_engine/audio_system.cpp

Lines changed: 404 additions & 231 deletions
Large diffs are not rendered by default.

attachments/simple_engine/audio_system.h

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44
#include <vector>
55
#include <memory>
66
#include <unordered_map>
7+
#include <thread>
8+
#include <mutex>
9+
#include <condition_variable>
10+
#include <atomic>
11+
#include <queue>
712
#ifdef __INTELLISENSE__
813
#include <vulkan/vulkan_raii.hpp>
914
#else
@@ -267,7 +272,7 @@ class AudioSystem {
267272
* @param sampleCount The number of samples to generate.
268273
* @param playbackPosition The current playback position for timing.
269274
*/
270-
void GenerateSineWavePing(float* buffer, uint32_t sampleCount, uint32_t playbackPosition);
275+
static void GenerateSineWavePing(float* buffer, uint32_t sampleCount, uint32_t playbackPosition);
271276

272277
private:
273278
// Loaded audio data
@@ -300,6 +305,36 @@ class AudioSystem {
300305
// Audio output device for sending processed audio to speakers
301306
std::unique_ptr<AudioOutputDevice> outputDevice = nullptr;
302307

308+
// Threading infrastructure for background audio processing
309+
std::thread audioThread;
310+
std::mutex audioMutex;
311+
std::condition_variable audioCondition;
312+
std::atomic<bool> audioThreadRunning{false};
313+
std::atomic<bool> audioThreadShouldStop{false};
314+
315+
// Audio processing task queue
316+
struct AudioTask {
317+
std::vector<float> inputBuffer;
318+
std::vector<float> outputBuffer;
319+
float sourcePosition[3];
320+
uint32_t sampleCount;
321+
uint32_t actualSamplesProcessed;
322+
AudioOutputDevice* outputDevice;
323+
float masterVolume;
324+
};
325+
// Set up HRTF parameters
326+
struct HRTFParams {
327+
float sourcePosition[3];
328+
float listenerPosition[3];
329+
float listenerOrientation[6]; // Forward (3) and up (3) vectors
330+
uint32_t sampleCount;
331+
uint32_t hrtfSize;
332+
uint32_t numHrtfPositions;
333+
float padding; // For alignment
334+
} params;
335+
std::queue<std::shared_ptr<AudioTask>> audioTaskQueue;
336+
std::mutex taskQueueMutex;
337+
303338
// Vulkan resources for HRTF processing
304339
vk::raii::Buffer inputBuffer = nullptr;
305340
vk::raii::DeviceMemory inputBufferMemory = nullptr;
@@ -310,6 +345,10 @@ class AudioSystem {
310345
vk::raii::Buffer paramsBuffer = nullptr;
311346
vk::raii::DeviceMemory paramsBufferMemory = nullptr;
312347

348+
// Persistent memory mapping for UBO to avoid repeated map/unmap operations
349+
void* persistentParamsMemory = nullptr;
350+
uint32_t currentSampleCount = 0; // Track current buffer size to avoid unnecessary recreation
351+
313352
/**
314353
* @brief Create buffers for HRTF processing.
315354
* @param sampleCount The number of samples to process.
@@ -321,4 +360,36 @@ class AudioSystem {
321360
* @brief Clean up HRTF buffers.
322361
*/
323362
void cleanupHRTFBuffers();
363+
364+
365+
/**
366+
* @brief Start the background audio processing thread.
367+
*/
368+
void startAudioThread();
369+
370+
/**
371+
* @brief Stop the background audio processing thread.
372+
*/
373+
void stopAudioThread();
374+
375+
/**
376+
* @brief Main loop for the background audio processing thread.
377+
*/
378+
void audioThreadLoop();
379+
380+
/**
381+
* @brief Process an audio task in the background thread.
382+
* @param task The audio task to process.
383+
*/
384+
void processAudioTask(const std::shared_ptr<AudioTask>& task);
385+
386+
/**
387+
* @brief Submit an audio processing task to the background thread.
388+
* @param inputBuffer The input audio buffer.
389+
* @param sampleCount The number of samples to process.
390+
* @param sourcePosition The position of the sound source.
391+
* @param actualSamplesProcessed The number of samples actually processed.
392+
* @return True if the task was submitted successfully, false otherwise.
393+
*/
394+
bool submitAudioTask(const float* inputBuffer, uint32_t sampleCount, const float* sourcePosition, uint32_t actualSamplesProcessed);
324395
};

attachments/simple_engine/renderer.h

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import vulkan_hpp;
1111
#include <string>
1212
#include <optional>
1313
#include <unordered_map>
14+
#include <mutex>
1415

1516
#include "platform.h"
1617
#include "entity.h"
@@ -119,10 +120,11 @@ class Renderer {
119120
* @param outputBuffer The output buffer.
120121
* @param hrtfBuffer The HRTF data buffer.
121122
* @param paramsBuffer The parameters buffer.
123+
* @return A fence that can be used to synchronize with the compute operation.
122124
*/
123-
void DispatchCompute(uint32_t groupCountX, uint32_t groupCountY, uint32_t groupCountZ,
124-
vk::Buffer inputBuffer, vk::Buffer outputBuffer,
125-
vk::Buffer hrtfBuffer, vk::Buffer paramsBuffer);
125+
vk::raii::Fence DispatchCompute(uint32_t groupCountX, uint32_t groupCountY, uint32_t groupCountZ,
126+
vk::Buffer inputBuffer, vk::Buffer outputBuffer,
127+
vk::Buffer hrtfBuffer, vk::Buffer paramsBuffer);
126128

127129
/**
128130
* @brief Check if the renderer is initialized.
@@ -146,7 +148,10 @@ class Renderer {
146148
* @brief Get the compute queue.
147149
* @return The compute queue.
148150
*/
149-
vk::Queue GetComputeQueue() const { return *computeQueue; }
151+
vk::Queue GetComputeQueue() const {
152+
std::lock_guard<std::mutex> lock(queueMutex);
153+
return *computeQueue;
154+
}
150155

151156
/**
152157
* @brief Find a suitable memory type.
@@ -158,6 +163,32 @@ class Renderer {
158163
return findMemoryType(typeFilter, properties);
159164
}
160165

166+
/**
167+
* @brief Get the compute queue family index.
168+
* @return The compute queue family index.
169+
*/
170+
uint32_t GetComputeQueueFamilyIndex() const {
171+
return queueFamilyIndices.computeFamily.value();
172+
}
173+
174+
/**
175+
* @brief Submit a command buffer to the compute queue with proper dispatch loader preservation.
176+
* @param commandBuffer The command buffer to submit.
177+
* @param fence The fence to signal when the operation completes.
178+
*/
179+
void SubmitToComputeQueue(vk::CommandBuffer commandBuffer, vk::Fence fence) {
180+
vk::SubmitInfo submitInfo{
181+
.commandBufferCount = 1,
182+
.pCommandBuffers = &commandBuffer
183+
};
184+
185+
// Use mutex to ensure thread-safe access to compute queue
186+
{
187+
std::lock_guard<std::mutex> lock(queueMutex);
188+
computeQueue.submit(submitInfo, fence);
189+
}
190+
}
191+
161192
/**
162193
* @brief Create a shader module from SPIR-V code.
163194
* @param code The SPIR-V code.
@@ -278,6 +309,10 @@ class Renderer {
278309
vk::raii::DescriptorSetLayout computeDescriptorSetLayout = nullptr;
279310
vk::raii::DescriptorPool computeDescriptorPool = nullptr;
280311
std::vector<vk::raii::DescriptorSet> computeDescriptorSets;
312+
vk::raii::CommandPool computeCommandPool = nullptr;
313+
314+
// Thread safety for queue access - unified mutex since queues may share the same underlying VkQueue
315+
mutable std::mutex queueMutex;
281316

282317
// Command pool and buffers
283318
vk::raii::CommandPool commandPool = nullptr;
@@ -385,6 +420,7 @@ class Renderer {
385420
bool createComputePipeline();
386421
void pushMaterialProperties(vk::CommandBuffer commandBuffer, const MaterialProperties& material);
387422
bool createCommandPool();
423+
bool createComputeCommandPool();
388424
bool createDepthResources();
389425
bool createTextureImage(const std::string& texturePath, TextureResources& resources);
390426
bool createTextureImageView(TextureResources& resources);

attachments/simple_engine/renderer_compute.cpp

Lines changed: 55 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
bool Renderer::createComputePipeline() {
1010
try {
1111
// Read compute shader code
12-
auto computeShaderCode = readFile("shaders/compute.spv");
12+
auto computeShaderCode = readFile("shaders/hrtf.spv");
1313

1414
// Create shader module
1515
vk::raii::ShaderModule computeShaderModule = createShaderModule(computeShaderCode);
@@ -99,18 +99,38 @@ bool Renderer::createComputePipeline() {
9999

100100
computeDescriptorPool = vk::raii::DescriptorPool(device, poolInfo);
101101

102-
return true;
102+
return createComputeCommandPool();
103103
} catch (const std::exception& e) {
104104
std::cerr << "Failed to create compute pipeline: " << e.what() << std::endl;
105105
return false;
106106
}
107107
}
108108

109+
// Create compute command pool
110+
bool Renderer::createComputeCommandPool() {
111+
try {
112+
vk::CommandPoolCreateInfo poolInfo{
113+
.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer,
114+
.queueFamilyIndex = queueFamilyIndices.computeFamily.value()
115+
};
116+
117+
computeCommandPool = vk::raii::CommandPool(device, poolInfo);
118+
return true;
119+
} catch (const std::exception& e) {
120+
std::cerr << "Failed to create compute command pool: " << e.what() << std::endl;
121+
return false;
122+
}
123+
}
124+
109125
// Dispatch compute shader
110-
void Renderer::DispatchCompute(uint32_t groupCountX, uint32_t groupCountY, uint32_t groupCountZ,
111-
vk::Buffer inputBuffer, vk::Buffer outputBuffer,
112-
vk::Buffer hrtfBuffer, vk::Buffer paramsBuffer) {
126+
vk::raii::Fence Renderer::DispatchCompute(uint32_t groupCountX, uint32_t groupCountY, uint32_t groupCountZ,
127+
vk::Buffer inputBuffer, vk::Buffer outputBuffer,
128+
vk::Buffer hrtfBuffer, vk::Buffer paramsBuffer) {
113129
try {
130+
// Create fence for synchronization
131+
vk::FenceCreateInfo fenceInfo{};
132+
vk::raii::Fence computeFence(device, fenceInfo);
133+
114134
// Create descriptor sets
115135
vk::DescriptorSetAllocateInfo allocInfo{
116136
.descriptorPool = *computeDescriptorPool,
@@ -182,47 +202,62 @@ void Renderer::DispatchCompute(uint32_t groupCountX, uint32_t groupCountY, uint3
182202

183203
device.updateDescriptorSets(descriptorWrites, {});
184204

185-
// Create command buffer
205+
// Create command buffer using dedicated compute command pool
186206
vk::CommandBufferAllocateInfo cmdAllocInfo{
187-
.commandPool = *commandPool,
207+
.commandPool = *computeCommandPool,
188208
.level = vk::CommandBufferLevel::ePrimary,
189209
.commandBufferCount = 1
190210
};
191211

192212
auto commandBuffers = device.allocateCommandBuffers(cmdAllocInfo);
193-
vk::CommandBuffer cmdBuffer = commandBuffers[0];
194-
vk::raii::CommandBuffer commandBuffer(device, cmdBuffer, *commandPool);
213+
// Use RAII wrapper temporarily for recording to preserve dispatch loader
214+
vk::raii::CommandBuffer commandBufferRaii = std::move(commandBuffers[0]);
195215

196216
// Begin command buffer
197217
vk::CommandBufferBeginInfo beginInfo{
198218
.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit
199219
};
200220

201-
commandBuffer.begin(beginInfo);
221+
commandBufferRaii.begin(beginInfo);
202222

203223
// Bind compute pipeline
204-
commandBuffer.bindPipeline(vk::PipelineBindPoint::eCompute, *computePipeline);
224+
commandBufferRaii.bindPipeline(vk::PipelineBindPoint::eCompute, *computePipeline);
205225

206-
// Bind descriptor sets
207-
commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eCompute, *computePipelineLayout, 0, reinterpret_cast<std::vector<vk::DescriptorSet> &>(computeDescriptorSets), {});
226+
// Bind descriptor sets - properly convert RAII descriptor set to regular descriptor set
227+
std::vector<vk::DescriptorSet> descriptorSetsToBindRaw;
228+
descriptorSetsToBindRaw.reserve(1);
229+
descriptorSetsToBindRaw.push_back(*computeDescriptorSets[0]);
230+
commandBufferRaii.bindDescriptorSets(vk::PipelineBindPoint::eCompute, *computePipelineLayout, 0, descriptorSetsToBindRaw, {});
208231

209232
// Dispatch compute shader
210-
commandBuffer.dispatch(groupCountX, groupCountY, groupCountZ);
233+
commandBufferRaii.dispatch(groupCountX, groupCountY, groupCountZ);
211234

212235
// End command buffer
213-
commandBuffer.end();
236+
commandBufferRaii.end();
237+
238+
// Extract raw command buffer for submission and release RAII ownership
239+
// This prevents premature destruction while preserving the recorded commands
240+
vk::CommandBuffer rawCommandBuffer = *commandBufferRaii;
241+
commandBufferRaii.release(); // Release RAII ownership to prevent destruction
214242

215-
// Submit command buffer
243+
// Submit command buffer with fence for synchronization
216244
vk::SubmitInfo submitInfo{
217245
.commandBufferCount = 1,
218-
.pCommandBuffers = &*commandBuffer
246+
.pCommandBuffers = &rawCommandBuffer
219247
};
220248

221-
computeQueue.submit(submitInfo, nullptr);
249+
// Use mutex to ensure thread-safe access to compute queue
250+
{
251+
std::lock_guard<std::mutex> lock(queueMutex);
252+
computeQueue.submit(submitInfo, *computeFence);
253+
}
222254

223-
// Wait for compute to complete
224-
computeQueue.waitIdle();
255+
// Return fence for non-blocking synchronization
256+
return computeFence;
225257
} catch (const std::exception& e) {
226258
std::cerr << "Failed to dispatch compute shader: " << e.what() << std::endl;
259+
// Return a null fence on error
260+
vk::FenceCreateInfo fenceInfo{};
261+
return vk::raii::Fence(device, fenceInfo);
227262
}
228263
}

attachments/simple_engine/renderer_core.cpp

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,21 @@ static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallbackVkRaii(
2121
const vk::DebugUtilsMessengerCallbackDataEXT* pCallbackData,
2222
void* pUserData) {
2323

24-
if (messageSeverity >= vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) {
25-
// Print message to console
26-
std::cerr << "Validation layer: " << pCallbackData->pMessage << std::endl;
27-
}
24+
// // Check if this is a shader debug printf message
25+
// if (messageType & vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation) {
26+
// std::string message(pCallbackData->pMessage);
27+
// if (message.find("DEBUG-PRINTF") != std::string::npos) {
28+
// // This is a shader debug printf message - always show it
29+
// std::cout << "FINDME ===== SHADER DEBUG: " << pCallbackData->pMessage << std::endl;
30+
// return VK_FALSE;
31+
// }
32+
// }
33+
printf("Received %s\n", pCallbackData->pMessage);
34+
35+
// if (messageSeverity >= vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) {
36+
// // Print message to console
37+
// std::cerr << "Validation layer: " << pCallbackData->pMessage << std::endl;
38+
// }
2839

2940
return VK_FALSE;
3041
}
@@ -222,6 +233,9 @@ bool Renderer::createInstance(const std::string& appName, bool enableValidationL
222233
};
223234

224235
// Enable validation layers if requested
236+
vk::ValidationFeaturesEXT validationFeatures{};
237+
std::vector<vk::ValidationFeatureEnableEXT> enabledValidationFeatures;
238+
225239
if (enableValidationLayers) {
226240
if (!checkValidationLayerSupport()) {
227241
std::cerr << "Validation layers requested, but not available" << std::endl;
@@ -230,6 +244,14 @@ bool Renderer::createInstance(const std::string& appName, bool enableValidationL
230244

231245
createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
232246
createInfo.ppEnabledLayerNames = validationLayers.data();
247+
248+
// Enable debug printf functionality for shader debugging
249+
enabledValidationFeatures.push_back(vk::ValidationFeatureEnableEXT::eDebugPrintf);
250+
251+
validationFeatures.enabledValidationFeatureCount = static_cast<uint32_t>(enabledValidationFeatures.size());
252+
validationFeatures.pEnabledValidationFeatures = enabledValidationFeatures.data();
253+
254+
createInfo.pNext = &validationFeatures;
233255
}
234256

235257
// Create instance
@@ -251,6 +273,7 @@ bool Renderer::setupDebugMessenger(bool enableValidationLayers) {
251273
// Create debug messenger info
252274
vk::DebugUtilsMessengerCreateInfoEXT createInfo{
253275
.messageSeverity = vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose |
276+
vk::DebugUtilsMessageSeverityFlagBitsEXT::eInfo |
254277
vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning |
255278
vk::DebugUtilsMessageSeverityFlagBitsEXT::eError,
256279
.messageType = vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral |

0 commit comments

Comments
 (0)