Skip to content

Commit

Permalink
partialy implemented glBlendFuncSeparate inside SDL2 for missing GLdc…
Browse files Browse the repository at this point in the history
… function, to support transparency alpha blending. updated Audio driver
  • Loading branch information
GPF committed Jan 24, 2025
1 parent 9003ceb commit 796910f
Show file tree
Hide file tree
Showing 21 changed files with 822 additions and 397 deletions.
2 changes: 1 addition & 1 deletion build-scripts/dreamcast.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
SOURCE_DIR="${PWD}/.."
BUILD_DIR="${PWD}/build"
# Default options
ENABLE_OPENGL=OFF
ENABLE_OPENGL=ON
ENABLE_SDL_TESTS=ON
ENABLE_PTHREADS=OFF
ENABLE_UNIX_TIMERS=ON
Expand Down
299 changes: 193 additions & 106 deletions src/audio/dreamcast/SDL_dreamcastaudio.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include "../SDL_audio_c.h"
#include "SDL_dreamcastaudio.h" // Defines SDL_PrivateAudioData struct
#include <dc/sound/stream.h> // Include KOS sound stream library
#include <dc/sound/sound.h>
#include <kos/thread.h>
#include "../SDL_sysaudio.h"
#include "SDL_timer.h"
Expand All @@ -35,116 +36,187 @@
static SDL_AudioDevice *audioDevice = NULL; // Pointer to the active audio output device
static SDL_AudioDevice *captureDevice = NULL; // Pointer to the active audio capture device

// Helper function to provide writable buffer to the client.
// This function is now named SDL_DC_SetSoundBuffer and provides a pointer
// to the writable part of the circular buffer.
void SDL_DC_SetSoundBuffer(Uint8 **buffer_ptr, int *available_size) {
SDL_AudioDevice *device = audioDevice;
SDL_PrivateAudioData *hidden = (SDL_PrivateAudioData *)device->hidden;
static Uint8 * DREAMCASTAUD_GetDeviceBuf(_THIS) {
SDL_PrivateAudioData *hidden = (SDL_PrivateAudioData *)_this->hidden;
return hidden->mixbuf[hidden->buffer];
}

// Calculate writable bytes
int writable_bytes;
if (hidden->write_index >= hidden->read_index) {
writable_bytes = hidden->buffer_size - hidden->write_index;
} else {
writable_bytes = hidden->read_index - hidden->write_index;
}
// // Stream callback function
// static void *stream_callback(snd_stream_hnd_t hnd, int req, int *done) {
// SDL_AudioDevice *device = audioDevice;
// SDL_PrivateAudioData *hidden = (SDL_PrivateAudioData *)device->hidden;
// Uint8 *buffer = (Uint8 *)DREAMCASTAUD_GetDeviceBuf(device);
// int buffer_size = hidden->buffer_size;
// int bytes_to_copy = SDL_min(req, buffer_size);
// int remaining = bytes_to_copy;
// SDL_AudioCallback callback;

// SDL_LockMutex(device->mixer_lock);

// if (!buffer) {
// SDL_Log("Buffer is NULL.");
// *done = 0;
// SDL_UnlockMutex(device->mixer_lock);
// return NULL;
// }

// callback = device->callbackspec.callback;

// // Handle paused or disabled audio
// if (!SDL_AtomicGet(&device->enabled) || SDL_AtomicGet(&device->paused)) {
// if (device->stream) {
// SDL_AudioStreamClear(device->stream);
// }
// SDL_memset(buffer, device->callbackspec.silence, bytes_to_copy);
// } else
// // {
// // if (hidden->direct_buffer_access) {
// // Uint8 *writable_buffer = NULL;
// // int writable_size = 0;

// // SDL_DC_SetSoundBuffer(&writable_buffer, &writable_size);
// // int bytes_to_copy = SDL_min(req, writable_size);

// // // Call the client callback directly to fill the buffer
// // device->callbackspec.callback(device->callbackspec.userdata, writable_buffer, bytes_to_copy);
// // hidden->write_index = (hidden->write_index + bytes_to_copy) % hidden->buffer_size;

*available_size = writable_bytes;

// // }
// // else
// {
// // Fall back to the existing behavior with memcpy
// if (!device->stream) {
// // callback(device->callbackspec.userdata, buffer, bytes_to_copy);
// } else {
// while (SDL_AudioStreamAvailable(device->stream) < bytes_to_copy) {
// // callback(device->callbackspec.userdata, hidden->buffer, buffer_size);
// if (SDL_AudioStreamPut(device->stream, buffer, buffer_size) == -1) {
// SDL_AudioStreamClear(device->stream);
// SDL_AtomicSet(&device->enabled, 0);
// break;
// }
// }

// int got = SDL_AudioStreamGet(device->stream, buffer, bytes_to_copy);
// if (got != bytes_to_copy) {
// SDL_memset(buffer, device->callbackspec.silence, bytes_to_copy);
// }
// }
// }


// Return the writable part of the buffer
*buffer_ptr = (Uint8 *)hidden->buffer + hidden->write_index;
}
// SDL_UnlockMutex(device->mixer_lock);

// *done = req;
// return buffer;
// }

// Stream callback function
static void *stream_callback(snd_stream_hnd_t hnd, int req, int *done) {
SDL_AudioDevice *device = audioDevice;
SDL_PrivateAudioData *hidden = (SDL_PrivateAudioData *)device->hidden;
Uint8 *buffer = (Uint8 *)hidden->buffer;
int buffer_size = hidden->buffer_size;
int bytes_to_copy = SDL_min(req, buffer_size);
int remaining = bytes_to_copy;
SDL_AudioCallback callback;

// SDL_Log("stream_callback");
SDL_LockMutex(device->mixer_lock);
// Obtain the buffer from SDL's device buffer
Uint8 *buffer = (Uint8 *)DREAMCASTAUD_GetDeviceBuf(device);
SDL_UnlockMutex(device->mixer_lock);
*done = bytes_to_copy;
return buffer;

}

if (!buffer) {
SDL_Log("Buffer is NULL.");
*done = 0;
SDL_UnlockMutex(device->mixer_lock);
return NULL;
}

callback = device->callbackspec.callback;

// Handle paused or disabled audio
if (!SDL_AtomicGet(&device->enabled) || SDL_AtomicGet(&device->paused)) {
if (device->stream) {
SDL_AudioStreamClear(device->stream);
}
SDL_memset(buffer, device->callbackspec.silence, bytes_to_copy);
} else {
if (hidden->direct_buffer_access) {
Uint8 *writable_buffer = NULL;
int writable_size = 0;

SDL_DC_SetSoundBuffer(&writable_buffer, &writable_size);
int bytes_to_copy = SDL_min(req, writable_size);

// Call the client callback directly to fill the buffer
device->callbackspec.callback(device->callbackspec.userdata, writable_buffer, bytes_to_copy);
hidden->write_index = (hidden->write_index + bytes_to_copy) % hidden->buffer_size;
// Thread function for audio playback
// static int SDLCALL DREAMCASTAUD_Thread(void *data) {
// SDL_AudioDevice *device = (SDL_AudioDevice *)data;
// SDL_PrivateAudioData *hidden = (SDL_PrivateAudioData *)device->hidden;

// SDL_Log("Audio thread started\n");

// while (SDL_AtomicGet(&device->enabled)) {
// // Call snd_stream_poll to check if more audio data is needed
// SDL_Log("snd_stream_poll");
// int result = snd_stream_poll(hidden->stream_handle);
// if (result < 0) {
// SDL_Log("snd_stream_poll failed: %d\n", result);
// SDL_AtomicSet(&device->enabled, 0);
// break;
// }
// SDL_Delay(10); // Avoid busy looping
// }

// SDL_Log("Audio thread exiting\n");

// return 0;
// }

static void DREAMCASTAUD_WaitDevice(_THIS) {
SDL_PrivateAudioData *hidden = (SDL_PrivateAudioData *)_this->hidden;

} else {
// Fall back to the existing behavior with memcpy
if (!device->stream) {
callback(device->callbackspec.userdata, buffer, bytes_to_copy);
} else {
while (SDL_AudioStreamAvailable(device->stream) < bytes_to_copy) {
callback(device->callbackspec.userdata, hidden->buffer, buffer_size);
if (SDL_AudioStreamPut(device->stream, hidden->buffer, buffer_size) == -1) {
SDL_AudioStreamClear(device->stream);
SDL_AtomicSet(&device->enabled, 0);
break;
}
}

int got = SDL_AudioStreamGet(device->stream, buffer, bytes_to_copy);
if (got != bytes_to_copy) {
SDL_memset(buffer, device->callbackspec.silence, bytes_to_copy);
}
}
}
if (SDL_AtomicGet(&_this->paused)) {
// SDL_Log("Audio is not enabled or is paused, skipping buffer switch.");
return;
}
// Get the current buffer pointer
Uint8 *current_buffer = DREAMCASTAUD_GetDeviceBuf(_this);

// Wait until the position moves to the next buffer
while (snd_get_pos(hidden->stream_handle) / (_this->spec.samples ) == (current_buffer == hidden->mixbuf[0] ? 0 : 1)) {
snd_stream_poll(hidden->stream_handle);
SDL_Delay(5);
// SDL_Log("DREAMCASTAUD_WaitDevice - waiting for buffer switch %d",snd_get_pos(hidden->stream_handle));
}
}
static void DREAMCASTAUD_PlayDevice(_THIS) {
SDL_PrivateAudioData *hidden = (SDL_PrivateAudioData *)_this->hidden;

// Check if audio is enabled and not paused
if (SDL_AtomicGet(&_this->paused)) {
// SDL_Log("Audio is not enabled or is paused, skipping buffer switch.");
return;
}

SDL_UnlockMutex(device->mixer_lock);
// SDL_Log("DREAMCASTAUD_PlayDevice");
DREAMCASTAUD_WaitDevice(_this); // Wait for the current buffer to be played

*done = req;
return buffer;
// Switch buffer for the next fill by SDL2
hidden->buffer ^= 1; // Toggle between 0 and 1 for buffer index
}

// Thread function for audio playback
static int SDLCALL DREAMCASTAUD_Thread(void *data) {
SDL_AudioDevice *device = (SDL_AudioDevice *)data;
SDL_PrivateAudioData *hidden = (SDL_PrivateAudioData *)device->hidden;

SDL_Log("Audio thread started\n");
static void DREAMCASTAUD_ThreadInit(_THIS) {
// SDL_Log("DREAMCASTAUD_ThreadInit");
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH);
}

while (SDL_AtomicGet(&device->enabled)) {
// Call snd_stream_poll to check if more audio data is needed
int result = snd_stream_poll(hidden->stream_handle);
if (result < 0) {
SDL_Log("snd_stream_poll failed: %d\n", result);
SDL_AtomicSet(&device->enabled, 0);
break;
// Mono-to-stereo filter function
void mono_to_stereo_filter(snd_stream_hnd_t hnd, void *obj, int hz, int channels, void **buffer, int *samplecnt) {
// Only perform conversion if the audio is mono (1 channel)
// if (channels == 1) {
SDL_Log("mono_to_stereo_filter");
int samples = *samplecnt;
Uint8 *input_buffer = (Uint8 *)(*buffer);

// Allocate a buffer for stereo output (twice the size of the input buffer)
Uint8 *stereo_buffer = malloc(samples);
if (!stereo_buffer) {
SDL_SetError("Failed to allocate stereo buffer");
return;
}
SDL_Delay(10); // Avoid busy looping
}

SDL_Log("Audio thread exiting\n");
// Duplicate mono samples to both left and right channels
for (int i = 0; i < samples; i++) {
stereo_buffer[i * 2] = input_buffer[i]; // Left channel
stereo_buffer[i * 2 + 1] = input_buffer[i]; // Right channel
}

return 0;
// Update buffer and sample count to reflect stereo data
*buffer = stereo_buffer;
*samplecnt = samples ; // Update to reflect stereo sample count
// }
}

// Open the audio device
Expand Down Expand Up @@ -196,33 +268,42 @@ int DREAMCASTAUD_OpenDevice(_THIS, const char *devname) {

SDL_CalculateAudioSpec(&_this->spec);

hidden->buffer_size = _this->spec.size; // The size remains the same
hidden->read_index = 0; // Initialize read index
hidden->write_index = 0; // Initialize write index
hidden->buffer_size = _this->spec.size* ((_this->spec.channels == 1) ? 2 : 1);

// Allocate the stream with the data callback
hidden->stream_handle = snd_stream_alloc(NULL, _this->spec.size);
hidden->stream_handle = snd_stream_alloc(NULL, hidden->buffer_size);
if (hidden->stream_handle == SND_STREAM_INVALID) {
SDL_free(hidden);
snd_stream_shutdown();
return SDL_SetError("Failed to allocate sound stream");
}

// Allocate a single buffer for reuse
hidden->buffer = (Uint8 *)memalign(32, _this->spec.size); // Ensure 32-byte alignment
if (!hidden->buffer) {
hidden->mixbuf[0] = (Uint8 *)memalign(32, hidden->buffer_size);
hidden->mixbuf[1] = (Uint8 *)memalign(32, hidden->buffer_size);
if (!hidden->mixbuf[0] || !hidden->mixbuf[1]) {
SDL_free(hidden);
snd_stream_shutdown();
return SDL_OutOfMemory();
}

SDL_memset(hidden->mixbuf[0], _this->spec.silence, hidden->buffer_size);
SDL_memset(hidden->mixbuf[1], _this->spec.silence, hidden->buffer_size);

// Set stream callback and start playback
snd_stream_reinit(hidden->stream_handle, stream_callback);
snd_stream_volume(hidden->stream_handle, 255); // Max volume

channels = _this->spec.channels;
frequency = _this->spec.freq;

// Set panning if it's mono
// if (channels == 1) {
// snd_stream_pan(hidden->stream_handle, 128, 128); // Centered mono panning
// // Add the mono-to-stereo filter to the stream
// snd_stream_filter_add(hidden->stream_handle, mono_to_stereo_filter, NULL);
// channels =2 ;
// }

// Check the audio format and initialize the stream accordingly
if (_this->spec.format == AUDIO_S16LSB) {
// Initialize stream for 16-bit PCM
Expand All @@ -234,15 +315,16 @@ int DREAMCASTAUD_OpenDevice(_THIS, const char *devname) {
SDL_SetError("Unsupported audio format: %d", _this->spec.format);
return -1;
}
// Start the playback thread
SDL_AtomicSet(&_this->enabled, 1); // Enable audio
_this->thread = SDL_CreateThread(DREAMCASTAUD_Thread, "DreamcastAudioThread", _this);
if (!_this->thread) {
SDL_AtomicSet(&_this->enabled, 0);
SDL_free(hidden);
snd_stream_shutdown();
return SDL_SetError("Failed to create audio thread");
}
// // Start the playback thread
// SDL_AtomicSet(&_this->enabled, 1); // Enable audio
// SDL_AtomicSet(&_this->paused, 0); // Enable audio
// _this->thread = SDL_CreateThread(DREAMCASTAUD_Thread, "DreamcastAudioThread", _this);
// if (!_this->thread) {
// SDL_AtomicSet(&_this->enabled, 0);
// SDL_free(hidden);
// snd_stream_shutdown();
// return SDL_SetError("Failed to create audio thread");
// }

SDL_Log("Dreamcast audio driver initialized\n");
return 0;
Expand All @@ -263,11 +345,11 @@ static void DREAMCASTAUD_CloseDevice(_THIS) {

if (hidden) {
if (hidden->stream_handle != SND_STREAM_INVALID) {
snd_stream_stop(hidden->stream_handle);
snd_stream_destroy(hidden->stream_handle);
}
if (hidden->buffer) {
SDL_free(hidden->buffer);
}
if (hidden->mixbuf[0]) SDL_free(hidden->mixbuf[0]);
if (hidden->mixbuf[1]) SDL_free(hidden->mixbuf[1]);
SDL_free(hidden);
_this->hidden = NULL;
}
Expand All @@ -285,8 +367,13 @@ static void DREAMCASTAUD_CloseDevice(_THIS) {
static SDL_bool DREAMCASTAUD_Init(SDL_AudioDriverImpl *impl) {
impl->OpenDevice = DREAMCASTAUD_OpenDevice;
impl->CloseDevice = DREAMCASTAUD_CloseDevice;
impl->PlayDevice = DREAMCASTAUD_PlayDevice;
impl->WaitDevice = DREAMCASTAUD_WaitDevice;
impl->GetDeviceBuf = DREAMCASTAUD_GetDeviceBuf;
impl->CloseDevice = DREAMCASTAUD_CloseDevice;
impl->ThreadInit = DREAMCASTAUD_ThreadInit;
impl->OnlyHasDefaultOutputDevice = SDL_TRUE;
impl->ProvidesOwnCallbackThread = SDL_TRUE;

return SDL_TRUE;
}

Expand Down
Loading

0 comments on commit 796910f

Please sign in to comment.