diff options
| author | victorfisac <[email protected]> | 2016-07-18 14:08:34 +0200 |
|---|---|---|
| committer | victorfisac <[email protected]> | 2016-07-18 14:08:34 +0200 |
| commit | 7a09043cba1b7d0d59587f82cb0627c965d557b9 (patch) | |
| tree | 3096a90205a78fb3bb3d28faca2e101cdf479a52 /src | |
| parent | 9fea631bfbe3d24565d100b55bbb0c43ae737374 (diff) | |
| parent | f5f3b4e095d89cb196f9156ed8fe7da95d697267 (diff) | |
| download | raylib-7a09043cba1b7d0d59587f82cb0627c965d557b9.tar.gz raylib-7a09043cba1b7d0d59587f82cb0627c965d557b9.zip | |
Merge remote-tracking branch 'refs/remotes/raysan5/develop' into develop
Diffstat (limited to 'src')
| -rw-r--r-- | src/Makefile | 6 | ||||
| -rw-r--r-- | src/audio.c | 496 | ||||
| -rw-r--r-- | src/audio.h | 44 | ||||
| -rw-r--r-- | src/core.c | 11 | ||||
| -rw-r--r-- | src/raylib.h | 15 | ||||
| -rw-r--r-- | src/rlgl.c | 875 | ||||
| -rw-r--r-- | src/rlgl.h | 25 | ||||
| -rw-r--r-- | src/shader_distortion.h | 106 | ||||
| -rw-r--r-- | src/shader_standard.h (renamed from src/standard_shader.h) | 3 | ||||
| -rw-r--r-- | src/shapes.c | 29 | ||||
| -rw-r--r-- | src/text.c | 17 |
11 files changed, 992 insertions, 635 deletions
diff --git a/src/Makefile b/src/Makefile index 33b666b4..b37cccf8 100644 --- a/src/Makefile +++ b/src/Makefile @@ -49,10 +49,10 @@ ifeq ($(PLATFORM),PLATFORM_RPI) # define raylib graphics api to use (on RPI, OpenGL ES 2.0 must be used) GRAPHICS = GRAPHICS_API_OPENGL_ES2 else - # define raylib graphics api to use (OpenGL 1.1 by default) - GRAPHICS ?= GRAPHICS_API_OPENGL_11 + # define raylib graphics api to use (OpenGL 3.3 by default) + GRAPHICS ?= GRAPHICS_API_OPENGL_33 + #GRAPHICS = GRAPHICS_API_OPENGL_11 # Uncomment to use OpenGL 1.1 #GRAPHICS = GRAPHICS_API_OPENGL_21 # Uncomment to use OpenGL 2.1 - #GRAPHICS = GRAPHICS_API_OPENGL_33 # Uncomment to use OpenGL 3.3 endif ifeq ($(PLATFORM),PLATFORM_WEB) GRAPHICS = GRAPHICS_API_OPENGL_ES2 diff --git a/src/audio.c b/src/audio.c index a76a453f..38fefd12 100644 --- a/src/audio.c +++ b/src/audio.c @@ -2,13 +2,26 @@ * * raylib.audio * -* Basic functions to manage Audio: InitAudioDevice, LoadAudioFiles, PlayAudioFiles +* Basic functions to manage Audio: +* Manage audio device (init/close) +* Load and Unload audio files +* Play/Stop/Pause/Resume loaded audio +* Manage mixing channels +* Manage raw audio context * * Uses external lib: * OpenAL Soft - Audio device management lib (http://kcat.strangesoft.net/openal.html) * stb_vorbis - Ogg audio files loading (http://www.nothings.org/stb_vorbis/) +* jar_xm - XM module file loading +* jar_mod - MOD audio file loading * -* Copyright (c) 2014 Ramon Santamaria (@raysan5) +* Many thanks to Joshua Reisenauer (github: @kd7tck) for the following additions: +* XM audio module support (jar_xm) +* MOD audio module support (jar_mod) +* Mixing channels support +* Raw audio context support +* +* Copyright (c) 2014-2016 Ramon Santamaria (@raysan5) * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. @@ -68,9 +81,9 @@ //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- -#define MAX_STREAM_BUFFERS 2 // Number of buffers for each alSource -#define MAX_MIX_CHANNELS 4 // Number of open AL sources +#define MAX_STREAM_BUFFERS 2 // Number of buffers for each source #define MAX_MUSIC_STREAMS 2 // Number of simultanious music sources +#define MAX_MIX_CHANNELS 4 // Number of mix channels (OpenAL sources) #if defined(PLATFORM_RPI) || defined(PLATFORM_ANDROID) // NOTE: On RPI and Android should be lower to avoid frame-stalls @@ -86,10 +99,10 @@ // Types and Structures Definition //---------------------------------------------------------------------------------- -// Used to create custom audio streams that are not bound to a specific file. There can be -// no more than 4 concurrent mixchannels in use. This is due to each active mixc being tied to -// a dedicated mix channel. -typedef struct MixChannel_t { +// Used to create custom audio streams that are not bound to a specific file. +// There can be no more than 4 concurrent mixchannels in use. +// This is due to each active mixc being tied to a dedicated mix channel. +typedef struct MixChannel { unsigned short sampleRate; // default is 48000 unsigned char channels; // 1=mono,2=stereo unsigned char mixChannel; // 0-3 or mixA-mixD, each mix channel can receive up to one dedicated audio stream @@ -99,40 +112,40 @@ typedef struct MixChannel_t { ALenum alFormat; // OpenAL format specifier ALuint alSource; // OpenAL source ALuint alBuffer[MAX_STREAM_BUFFERS]; // OpenAL sample buffer -} MixChannel_t; +} MixChannel; // Music type (file streaming from memory) // NOTE: Anything longer than ~10 seconds should be streamed into a mix channel... typedef struct Music { stb_vorbis *stream; - jar_xm_context_t *xmctx; // XM chiptune context - jar_mod_context_t modctx; // MOD chiptune context - MixChannel_t *mixc; // mix channel + jar_xm_context_t *xmctx; // XM chiptune context + jar_mod_context_t modctx; // MOD chiptune context + MixChannel *mixc; // Mix channel unsigned int totalSamplesLeft; float totalLengthSeconds; bool loop; - bool chipTune; // chiptune is loaded? + bool chipTune; // chiptune is loaded? bool enabled; } Music; // Audio errors registered typedef enum { - ERROR_RAW_CONTEXT_CREATION = 1, - ERROR_XM_CONTEXT_CREATION = 2, - ERROR_MOD_CONTEXT_CREATION = 4, - ERROR_MIX_CHANNEL_CREATION = 8, - ERROR_MUSIC_CHANNEL_CREATION = 16, - ERROR_LOADING_XM = 32, - ERROR_LOADING_MOD = 64, - ERROR_LOADING_WAV = 128, - ERROR_LOADING_OGG = 256, - ERROR_OUT_OF_MIX_CHANNELS = 512, - ERROR_EXTENSION_NOT_RECOGNIZED = 1024, - ERROR_UNABLE_TO_OPEN_RRES_FILE = 2048, - ERROR_INVALID_RRES_FILE = 4096, - ERROR_INVALID_RRES_RESOURCE = 8192, - ERROR_UNINITIALIZED_CHANNELS = 16384 + ERROR_RAW_CONTEXT_CREATION = 1, + ERROR_XM_CONTEXT_CREATION = 2, + ERROR_MOD_CONTEXT_CREATION = 4, + ERROR_MIX_CHANNEL_CREATION = 8, + ERROR_MUSIC_CHANNEL_CREATION = 16, + ERROR_LOADING_XM = 32, + ERROR_LOADING_MOD = 64, + ERROR_LOADING_WAV = 128, + ERROR_LOADING_OGG = 256, + ERROR_OUT_OF_MIX_CHANNELS = 512, + ERROR_EXTENSION_NOT_RECOGNIZED = 1024, + ERROR_UNABLE_TO_OPEN_RRES_FILE = 2048, + ERROR_INVALID_RRES_FILE = 4096, + ERROR_INVALID_RRES_RESOURCE = 8192, + ERROR_UNINITIALIZED_CHANNELS = 16384 } AudioError; #if defined(AUDIO_STANDALONE) @@ -142,10 +155,10 @@ typedef enum { INFO = 0, ERROR, WARNING, DEBUG, OTHER } TraceLogType; //---------------------------------------------------------------------------------- // Global Variables Definition //---------------------------------------------------------------------------------- -static Music musicChannels_g[MAX_MUSIC_STREAMS]; // Current music loaded, up to two can play at the same time -static MixChannel_t *mixChannels_g[MAX_MIX_CHANNELS]; // What mix channels are currently active +static Music musicStreams[MAX_MUSIC_STREAMS]; // Current music loaded, up to two can play at the same time +static MixChannel *mixChannels[MAX_MIX_CHANNELS]; // Mix channels currently active (from music streams) -static int lastAudioError = 0; // Registers last audio error +static int lastAudioError = 0; // Registers last audio error //---------------------------------------------------------------------------------- // Module specific Functions Declaration @@ -157,13 +170,11 @@ static void UnloadWave(Wave wave); // Unload wave data static bool BufferMusicStream(int index, int numBuffers); // Fill music buffers with data static void EmptyMusicStream(int index); // Empty music buffers -static MixChannel_t *InitMixChannel(unsigned short sampleRate, unsigned char mixChannel, unsigned char channels, bool floatingPoint); // For streaming into mix channels. -static void CloseMixChannel(MixChannel_t *mixc); // Frees mix channel -static int BufferMixChannel(MixChannel_t *mixc, void *data, int numberElements); // Pushes more audio data into mixc mix channel, if NULL is passed it pauses -static int FillAlBufferWithSilence(MixChannel_t *mixc, ALuint buffer); // Fill buffer with zeros, returns number processed -static void ResampleShortToFloat(short *shorts, float *floats, unsigned short len); // Pass two arrays of the same legnth in -static void ResampleByteToFloat(char *chars, float *floats, unsigned short len); // Pass two arrays of same length in -static int IsMusicStreamReadyForBuffering(int index); // Checks if music buffer is ready to be refilled +static MixChannel *InitMixChannel(unsigned short sampleRate, unsigned char mixChannel, unsigned char channels, bool floatingPoint); +static void CloseMixChannel(MixChannel *mixc); // Frees mix channel +static int BufferMixChannel(MixChannel *mixc, void *data, int numberElements); // Pushes more audio data into mix channel +//static void ResampleShortToFloat(short *shorts, float *floats, unsigned short len); // Pass two arrays of the same legnth in +//static void ResampleByteToFloat(char *chars, float *floats, unsigned short len); // Pass two arrays of same length in #if defined(AUDIO_STANDALONE) const char *GetExtension(const char *fileName); // Get the extension for a filename @@ -204,9 +215,9 @@ void InitAudioDevice(void) // Close the audio device for all contexts void CloseAudioDevice(void) { - for (int index=0; index<MAX_MUSIC_STREAMS; index++) + for (int index = 0; index < MAX_MUSIC_STREAMS; index++) { - if (musicChannels_g[index].mixc) StopMusicStream(index); // Stop music streaming and close current stream + if (musicStreams[index].mixc) StopMusicStream(index); // Stop music streaming and close current stream } ALCdevice *device; @@ -221,7 +232,7 @@ void CloseAudioDevice(void) alcCloseDevice(device); } -// True if call to InitAudioDevice() was successful and CloseAudioDevice() has not been called yet +// Check if device has been initialized successfully bool IsAudioDeviceReady(void) { ALCcontext *context = alcGetCurrentContext(); @@ -240,22 +251,22 @@ bool IsAudioDeviceReady(void) // Module Functions Definition - Custom audio output //---------------------------------------------------------------------------------- -// For streaming into mix channels. -// The mixChannel is what audio muxing channel you want to operate on, 0-3 are the ones available. Each mix channel can only be used one at a time. -// exmple usage is InitMixChannel(48000, 0, 2, true); // mixchannel 1, 48khz, stereo, floating point -static MixChannel_t *InitMixChannel(unsigned short sampleRate, unsigned char mixChannel, unsigned char channels, bool floatingPoint) +// Init mix channel for streaming +// The mixChannel is what audio muxing channel you want to operate on, 0-3 are the ones available. +// Each mix channel can only be used one at a time. +static MixChannel *InitMixChannel(unsigned short sampleRate, unsigned char mixChannel, unsigned char channels, bool floatingPoint) { if (mixChannel >= MAX_MIX_CHANNELS) return NULL; if (!IsAudioDeviceReady()) InitAudioDevice(); - if (!mixChannels_g[mixChannel]) + if (!mixChannels[mixChannel]) { - MixChannel_t *mixc = (MixChannel_t *)malloc(sizeof(MixChannel_t)); + MixChannel *mixc = (MixChannel *)malloc(sizeof(MixChannel)); mixc->sampleRate = sampleRate; mixc->channels = channels; mixc->mixChannel = mixChannel; mixc->floatingPoint = floatingPoint; - mixChannels_g[mixChannel] = mixc; + mixChannels[mixChannel] = mixc; // Setup OpenAL format if (channels == 1) @@ -280,7 +291,20 @@ static MixChannel_t *InitMixChannel(unsigned short sampleRate, unsigned char mix alGenBuffers(MAX_STREAM_BUFFERS, mixc->alBuffer); // Fill buffers - for (int i = 0; i < MAX_STREAM_BUFFERS; i++) FillAlBufferWithSilence(mixc, mixc->alBuffer[i]); + for (int i = 0; i < MAX_STREAM_BUFFERS; i++) + { + // Initialize buffer with zeros by default + if (mixc->floatingPoint) + { + float pcm[MUSIC_BUFFER_SIZE_FLOAT] = { 0.0f }; + alBufferData(mixc->alBuffer[i], mixc->alFormat, pcm, MUSIC_BUFFER_SIZE_FLOAT*sizeof(float), mixc->sampleRate); + } + else + { + short pcm[MUSIC_BUFFER_SIZE_SHORT] = { 0 }; + alBufferData(mixc->alBuffer[i], mixc->alFormat, pcm, MUSIC_BUFFER_SIZE_SHORT*sizeof(short), mixc->sampleRate); + } + } alSourceQueueBuffers(mixc->alSource, MAX_STREAM_BUFFERS, mixc->alBuffer); mixc->playing = true; @@ -293,7 +317,7 @@ static MixChannel_t *InitMixChannel(unsigned short sampleRate, unsigned char mix } // Frees buffer in mix channel -static void CloseMixChannel(MixChannel_t *mixc) +static void CloseMixChannel(MixChannel *mixc) { if (mixc) { @@ -314,18 +338,18 @@ static void CloseMixChannel(MixChannel_t *mixc) // Delete source and buffers alDeleteSources(1, &mixc->alSource); alDeleteBuffers(MAX_STREAM_BUFFERS, mixc->alBuffer); - mixChannels_g[mixc->mixChannel] = NULL; + mixChannels[mixc->mixChannel] = NULL; free(mixc); mixc = NULL; } } -// Pushes more audio data into mixc mix channel, only one buffer per call +// Pushes more audio data into mix channel, only one buffer per call // Call "BufferMixChannel(mixc, NULL, 0)" if you want to pause the audio. -// @Returns number of samples that where processed. -static int BufferMixChannel(MixChannel_t *mixc, void *data, int numberElements) +// Returns number of samples that where processed. +static int BufferMixChannel(MixChannel *mixc, void *data, int numberElements) { - if (!mixc || (mixChannels_g[mixc->mixChannel] != mixc)) return 0; // When there is two channels there must be an even number of samples + if (!mixc || (mixChannels[mixc->mixChannel] != mixc)) return 0; // When there is two channels there must be an even number of samples if (!data || !numberElements) { @@ -368,28 +392,11 @@ static int BufferMixChannel(MixChannel_t *mixc, void *data, int numberElements) return numberElements; } -// fill buffer with zeros, returns number processed -static int FillAlBufferWithSilence(MixChannel_t *mixc, ALuint buffer) -{ - if (mixc->floatingPoint) - { - float pcm[MUSIC_BUFFER_SIZE_FLOAT] = { 0.0f }; - alBufferData(buffer, mixc->alFormat, pcm, MUSIC_BUFFER_SIZE_FLOAT*sizeof(float), mixc->sampleRate); - - return MUSIC_BUFFER_SIZE_FLOAT; - } - else - { - short pcm[MUSIC_BUFFER_SIZE_SHORT] = { 0 }; - alBufferData(buffer, mixc->alFormat, pcm, MUSIC_BUFFER_SIZE_SHORT*sizeof(short), mixc->sampleRate); - - return MUSIC_BUFFER_SIZE_SHORT; - } -} - +/* +// Convert data from short to float // example usage: -// short sh[3] = {1,2,3};float fl[3]; -// ResampleShortToFloat(sh,fl,3); +// short sh[3] = {1,2,3};float fl[3]; +// ResampleShortToFloat(sh,fl,3); static void ResampleShortToFloat(short *shorts, float *floats, unsigned short len) { for (int i = 0; i < len; i++) @@ -399,9 +406,10 @@ static void ResampleShortToFloat(short *shorts, float *floats, unsigned short le } } +// Convert data from float to short // example usage: -// char ch[3] = {1,2,3};float fl[3]; -// ResampleByteToFloat(ch,fl,3); +// char ch[3] = {1,2,3};float fl[3]; +// ResampleByteToFloat(ch,fl,3); static void ResampleByteToFloat(char *chars, float *floats, unsigned short len) { for (int i = 0; i < len; i++) @@ -410,43 +418,55 @@ static void ResampleByteToFloat(char *chars, float *floats, unsigned short len) else floats[i] = (float)chars[i]/128.0f; } } +*/ -// used to output raw audio streams, returns negative numbers on error, + number represents the mix channel index -// if floating point is false the data size is 16bit short, otherwise it is float 32bit -RawAudioContext InitRawAudioContext(int sampleRate, int channels, bool floatingPoint) +// Initialize raw audio mix channel for audio buffering +// NOTE: Returns mix channel index or -1 if it fails (errors are registered on lastAudioError) +int InitRawMixChannel(int sampleRate, int channels, bool floatingPoint) { int mixIndex; + for (mixIndex = 0; mixIndex < MAX_MIX_CHANNELS; mixIndex++) // find empty mix channel slot { - if (mixChannels_g[mixIndex] == NULL) break; - else if (mixIndex == (MAX_MIX_CHANNELS - 1)) return ERROR_OUT_OF_MIX_CHANNELS; // error + if (mixChannels[mixIndex] == NULL) break; + else if (mixIndex == (MAX_MIX_CHANNELS - 1)) + { + lastAudioError = ERROR_OUT_OF_MIX_CHANNELS; + return -1; + } } if (InitMixChannel(sampleRate, mixIndex, channels, floatingPoint)) return mixIndex; - else return ERROR_RAW_CONTEXT_CREATION; // error -} - -void CloseRawAudioContext(RawAudioContext ctx) -{ - if (mixChannels_g[ctx]) CloseMixChannel(mixChannels_g[ctx]); + else + { + lastAudioError = ERROR_RAW_CONTEXT_CREATION; + return -1; + } } -// if 0 is returned, the buffers are still full and you need to keep trying with the same data until a + number is returned. -// any + number returned is the number of samples that was processed and passed into buffer. -// data either needs to be array of floats or shorts. -int BufferRawAudioContext(RawAudioContext ctx, void *data, unsigned short numberElements) +// Buffers data directly to raw mix channel +// if 0 is returned, buffers are still full and you need to keep trying with the same data +// otherwise it will return number of samples buffered. +// NOTE: Data could be either be an array of floats or shorts, depending on the created context +int BufferRawAudioContext(int ctx, void *data, unsigned short numberElements) { int numBuffered = 0; if (ctx >= 0) { - MixChannel_t* mixc = mixChannels_g[ctx]; + MixChannel *mixc = mixChannels[ctx]; numBuffered = BufferMixChannel(mixc, data, numberElements); } return numBuffered; } +// Closes and frees raw mix channel +void CloseRawAudioContext(int ctx) +{ + if (mixChannels[ctx]) CloseMixChannel(mixChannels[ctx]); +} + //---------------------------------------------------------------------------------- // Module Functions Definition - Sounds loading and playing (.WAV) //---------------------------------------------------------------------------------- @@ -804,25 +824,25 @@ void SetSoundPitch(Sound sound, float pitch) //---------------------------------------------------------------------------------- // Start music playing (open stream) -// returns 0 on success +// returns 0 on success or error code int PlayMusicStream(int index, char *fileName) { int mixIndex; - if (musicChannels_g[index].stream || musicChannels_g[index].xmctx) return ERROR_UNINITIALIZED_CHANNELS; // error + if (musicStreams[index].stream || musicStreams[index].xmctx) return ERROR_UNINITIALIZED_CHANNELS; // error for (mixIndex = 0; mixIndex < MAX_MIX_CHANNELS; mixIndex++) // find empty mix channel slot { - if (mixChannels_g[mixIndex] == NULL) break; + if (mixChannels[mixIndex] == NULL) break; else if (mixIndex == (MAX_MIX_CHANNELS - 1)) return ERROR_OUT_OF_MIX_CHANNELS; // error } if (strcmp(GetExtension(fileName),"ogg") == 0) { // Open audio stream - musicChannels_g[index].stream = stb_vorbis_open_filename(fileName, NULL, NULL); + musicStreams[index].stream = stb_vorbis_open_filename(fileName, NULL, NULL); - if (musicChannels_g[index].stream == NULL) + if (musicStreams[index].stream == NULL) { TraceLog(WARNING, "[%s] OGG audio file could not be opened", fileName); return ERROR_LOADING_OGG; // error @@ -830,53 +850,53 @@ int PlayMusicStream(int index, char *fileName) else { // Get file info - stb_vorbis_info info = stb_vorbis_get_info(musicChannels_g[index].stream); + stb_vorbis_info info = stb_vorbis_get_info(musicStreams[index].stream); TraceLog(INFO, "[%s] Ogg sample rate: %i", fileName, info.sample_rate); TraceLog(INFO, "[%s] Ogg channels: %i", fileName, info.channels); TraceLog(DEBUG, "[%s] Temp memory required: %i", fileName, info.temp_memory_required); - musicChannels_g[index].loop = true; // We loop by default - musicChannels_g[index].enabled = true; + musicStreams[index].loop = true; // We loop by default + musicStreams[index].enabled = true; - musicChannels_g[index].totalSamplesLeft = (unsigned int)stb_vorbis_stream_length_in_samples(musicChannels_g[index].stream) * info.channels; - musicChannels_g[index].totalLengthSeconds = stb_vorbis_stream_length_in_seconds(musicChannels_g[index].stream); + musicStreams[index].totalSamplesLeft = (unsigned int)stb_vorbis_stream_length_in_samples(musicStreams[index].stream) * info.channels; + musicStreams[index].totalLengthSeconds = stb_vorbis_stream_length_in_seconds(musicStreams[index].stream); if (info.channels == 2) { - musicChannels_g[index].mixc = InitMixChannel(info.sample_rate, mixIndex, 2, false); - musicChannels_g[index].mixc->playing = true; + musicStreams[index].mixc = InitMixChannel(info.sample_rate, mixIndex, 2, false); + musicStreams[index].mixc->playing = true; } else { - musicChannels_g[index].mixc = InitMixChannel(info.sample_rate, mixIndex, 1, false); - musicChannels_g[index].mixc->playing = true; + musicStreams[index].mixc = InitMixChannel(info.sample_rate, mixIndex, 1, false); + musicStreams[index].mixc->playing = true; } - if (!musicChannels_g[index].mixc) return ERROR_LOADING_OGG; // error + if (!musicStreams[index].mixc) return ERROR_LOADING_OGG; // error } } else if (strcmp(GetExtension(fileName),"xm") == 0) { // only stereo is supported for xm - if (!jar_xm_create_context_from_file(&musicChannels_g[index].xmctx, 48000, fileName)) + if (!jar_xm_create_context_from_file(&musicStreams[index].xmctx, 48000, fileName)) { - musicChannels_g[index].chipTune = true; - musicChannels_g[index].loop = true; - jar_xm_set_max_loop_count(musicChannels_g[index].xmctx, 0); // infinite number of loops - musicChannels_g[index].totalSamplesLeft = (unsigned int)jar_xm_get_remaining_samples(musicChannels_g[index].xmctx); - musicChannels_g[index].totalLengthSeconds = ((float)musicChannels_g[index].totalSamplesLeft) / 48000.f; - musicChannels_g[index].enabled = true; + musicStreams[index].chipTune = true; + musicStreams[index].loop = true; + jar_xm_set_max_loop_count(musicStreams[index].xmctx, 0); // infinite number of loops + musicStreams[index].totalSamplesLeft = (unsigned int)jar_xm_get_remaining_samples(musicStreams[index].xmctx); + musicStreams[index].totalLengthSeconds = ((float)musicStreams[index].totalSamplesLeft)/48000.0f; + musicStreams[index].enabled = true; - TraceLog(INFO, "[%s] XM number of samples: %i", fileName, musicChannels_g[index].totalSamplesLeft); - TraceLog(INFO, "[%s] XM track length: %11.6f sec", fileName, musicChannels_g[index].totalLengthSeconds); + TraceLog(INFO, "[%s] XM number of samples: %i", fileName, musicStreams[index].totalSamplesLeft); + TraceLog(INFO, "[%s] XM track length: %11.6f sec", fileName, musicStreams[index].totalLengthSeconds); - musicChannels_g[index].mixc = InitMixChannel(48000, mixIndex, 2, true); + musicStreams[index].mixc = InitMixChannel(48000, mixIndex, 2, true); - if (!musicChannels_g[index].mixc) return ERROR_XM_CONTEXT_CREATION; // error + if (!musicStreams[index].mixc) return ERROR_XM_CONTEXT_CREATION; // error - musicChannels_g[index].mixc->playing = true; + musicStreams[index].mixc->playing = true; } else { @@ -886,24 +906,24 @@ int PlayMusicStream(int index, char *fileName) } else if (strcmp(GetExtension(fileName),"mod") == 0) { - jar_mod_init(&musicChannels_g[index].modctx); + jar_mod_init(&musicStreams[index].modctx); - if (jar_mod_load_file(&musicChannels_g[index].modctx, fileName)) + if (jar_mod_load_file(&musicStreams[index].modctx, fileName)) { - musicChannels_g[index].chipTune = true; - musicChannels_g[index].loop = true; - musicChannels_g[index].totalSamplesLeft = (unsigned int)jar_mod_max_samples(&musicChannels_g[index].modctx); - musicChannels_g[index].totalLengthSeconds = ((float)musicChannels_g[index].totalSamplesLeft) / 48000.f; - musicChannels_g[index].enabled = true; + musicStreams[index].chipTune = true; + musicStreams[index].loop = true; + musicStreams[index].totalSamplesLeft = (unsigned int)jar_mod_max_samples(&musicStreams[index].modctx); + musicStreams[index].totalLengthSeconds = ((float)musicStreams[index].totalSamplesLeft)/48000.0f; + musicStreams[index].enabled = true; - TraceLog(INFO, "[%s] MOD number of samples: %i", fileName, musicChannels_g[index].totalSamplesLeft); - TraceLog(INFO, "[%s] MOD track length: %11.6f sec", fileName, musicChannels_g[index].totalLengthSeconds); + TraceLog(INFO, "[%s] MOD number of samples: %i", fileName, musicStreams[index].totalSamplesLeft); + TraceLog(INFO, "[%s] MOD track length: %11.6f sec", fileName, musicStreams[index].totalLengthSeconds); - musicChannels_g[index].mixc = InitMixChannel(48000, mixIndex, 2, false); + musicStreams[index].mixc = InitMixChannel(48000, mixIndex, 2, false); - if (!musicChannels_g[index].mixc) return ERROR_MOD_CONTEXT_CREATION; // error + if (!musicStreams[index].mixc) return ERROR_MOD_CONTEXT_CREATION; // error - musicChannels_g[index].mixc->playing = true; + musicStreams[index].mixc->playing = true; } else { @@ -920,30 +940,75 @@ int PlayMusicStream(int index, char *fileName) return 0; // normal return } -// Stop music playing for individual music index of musicChannels_g array (close stream) +// Stop music playing for individual music index of musicStreams array (close stream) void StopMusicStream(int index) { - if (index < MAX_MUSIC_STREAMS && musicChannels_g[index].mixc) + if (index < MAX_MUSIC_STREAMS && musicStreams[index].mixc) { - CloseMixChannel(musicChannels_g[index].mixc); + CloseMixChannel(musicStreams[index].mixc); - if (musicChannels_g[index].xmctx) - jar_xm_free_context(musicChannels_g[index].xmctx); - else if (musicChannels_g[index].modctx.mod_loaded) - jar_mod_unload(&musicChannels_g[index].modctx); + if (musicStreams[index].xmctx) + jar_xm_free_context(musicStreams[index].xmctx); + else if (musicStreams[index].modctx.mod_loaded) + jar_mod_unload(&musicStreams[index].modctx); else - stb_vorbis_close(musicChannels_g[index].stream); + stb_vorbis_close(musicStreams[index].stream); - musicChannels_g[index].enabled = false; + musicStreams[index].enabled = false; - if (musicChannels_g[index].stream || musicChannels_g[index].xmctx) + if (musicStreams[index].stream || musicStreams[index].xmctx) { - musicChannels_g[index].stream = NULL; - musicChannels_g[index].xmctx = NULL; + musicStreams[index].stream = NULL; + musicStreams[index].xmctx = NULL; } } } +// Update (re-fill) music buffers if data already processed +void UpdateMusicStream(int index) +{ + ALenum state; + bool active = true; + ALint processed = 0; + + // Determine if music stream is ready to be written + alGetSourcei(musicStreams[index].mixc->alSource, AL_BUFFERS_PROCESSED, &processed); + + if (musicStreams[index].mixc->playing && (index < MAX_MUSIC_STREAMS) && musicStreams[index].enabled && musicStreams[index].mixc && (processed > 0)) + { + active = BufferMusicStream(index, processed); + + if (!active && musicStreams[index].loop) + { + if (musicStreams[index].chipTune) + { + if(musicStreams[index].modctx.mod_loaded) jar_mod_seek_start(&musicStreams[index].modctx); + + musicStreams[index].totalSamplesLeft = musicStreams[index].totalLengthSeconds*48000.0f; + } + else + { + stb_vorbis_seek_start(musicStreams[index].stream); + musicStreams[index].totalSamplesLeft = stb_vorbis_stream_length_in_samples(musicStreams[index].stream)*musicStreams[index].mixc->channels; + } + + // Determine if music stream is ready to be written + alGetSourcei(musicStreams[index].mixc->alSource, AL_BUFFERS_PROCESSED, &processed); + + active = BufferMusicStream(index, processed); + } + + if (alGetError() != AL_NO_ERROR) TraceLog(WARNING, "Error buffering data..."); + + alGetSourcei(musicStreams[index].mixc->alSource, AL_SOURCE_STATE, &state); + + if (state != AL_PLAYING && active) alSourcePlay(musicStreams[index].mixc->alSource); + + if (!active) StopMusicStream(index); + + } +} + //get number of music channels active at this time, this does not mean they are playing int GetMusicStreamCount(void) { @@ -952,7 +1017,7 @@ int GetMusicStreamCount(void) // Find empty music slot for (int musicIndex = 0; musicIndex < MAX_MUSIC_STREAMS; musicIndex++) { - if(musicChannels_g[musicIndex].stream != NULL || musicChannels_g[musicIndex].chipTune) musicCount++; + if(musicStreams[musicIndex].stream != NULL || musicStreams[musicIndex].chipTune) musicCount++; } return musicCount; @@ -962,11 +1027,11 @@ int GetMusicStreamCount(void) void PauseMusicStream(int index) { // Pause music stream if music available! - if (index < MAX_MUSIC_STREAMS && musicChannels_g[index].mixc && musicChannels_g[index].enabled) + if (index < MAX_MUSIC_STREAMS && musicStreams[index].mixc && musicStreams[index].enabled) { TraceLog(INFO, "Pausing music stream"); - alSourcePause(musicChannels_g[index].mixc->alSource); - musicChannels_g[index].mixc->playing = false; + alSourcePause(musicStreams[index].mixc->alSource); + musicStreams[index].mixc->playing = false; } } @@ -976,15 +1041,15 @@ void ResumeMusicStream(int index) // Resume music playing... if music available! ALenum state; - if (index < MAX_MUSIC_STREAMS && musicChannels_g[index].mixc) + if (index < MAX_MUSIC_STREAMS && musicStreams[index].mixc) { - alGetSourcei(musicChannels_g[index].mixc->alSource, AL_SOURCE_STATE, &state); + alGetSourcei(musicStreams[index].mixc->alSource, AL_SOURCE_STATE, &state); if (state == AL_PAUSED) { TraceLog(INFO, "Resuming music stream"); - alSourcePlay(musicChannels_g[index].mixc->alSource); - musicChannels_g[index].mixc->playing = true; + alSourcePlay(musicStreams[index].mixc->alSource); + musicStreams[index].mixc->playing = true; } } } @@ -995,9 +1060,9 @@ bool IsMusicPlaying(int index) bool playing = false; ALint state; - if (index < MAX_MUSIC_STREAMS && musicChannels_g[index].mixc) + if (index < MAX_MUSIC_STREAMS && musicStreams[index].mixc) { - alGetSourcei(musicChannels_g[index].mixc->alSource, AL_SOURCE_STATE, &state); + alGetSourcei(musicStreams[index].mixc->alSource, AL_SOURCE_STATE, &state); if (state == AL_PLAYING) playing = true; } @@ -1008,18 +1073,18 @@ bool IsMusicPlaying(int index) // Set volume for music void SetMusicVolume(int index, float volume) { - if (index < MAX_MUSIC_STREAMS && musicChannels_g[index].mixc) + if (index < MAX_MUSIC_STREAMS && musicStreams[index].mixc) { - alSourcef(musicChannels_g[index].mixc->alSource, AL_GAIN, volume); + alSourcef(musicStreams[index].mixc->alSource, AL_GAIN, volume); } } // Set pitch for music void SetMusicPitch(int index, float pitch) { - if (index < MAX_MUSIC_STREAMS && musicChannels_g[index].mixc) + if (index < MAX_MUSIC_STREAMS && musicStreams[index].mixc) { - alSourcef(musicChannels_g[index].mixc->alSource, AL_PITCH, pitch); + alSourcef(musicStreams[index].mixc->alSource, AL_PITCH, pitch); } } @@ -1028,8 +1093,8 @@ float GetMusicTimeLength(int index) { float totalSeconds; - if (musicChannels_g[index].chipTune) totalSeconds = (float)musicChannels_g[index].totalLengthSeconds; - else totalSeconds = stb_vorbis_stream_length_in_seconds(musicChannels_g[index].stream); + if (musicStreams[index].chipTune) totalSeconds = (float)musicStreams[index].totalLengthSeconds; + else totalSeconds = stb_vorbis_stream_length_in_seconds(musicStreams[index].stream); return totalSeconds; } @@ -1039,24 +1104,24 @@ float GetMusicTimePlayed(int index) { float secondsPlayed = 0.0f; - if (index < MAX_MUSIC_STREAMS && musicChannels_g[index].mixc) + if (index < MAX_MUSIC_STREAMS && musicStreams[index].mixc) { - if (musicChannels_g[index].chipTune && musicChannels_g[index].xmctx) + if (musicStreams[index].chipTune && musicStreams[index].xmctx) { uint64_t samples; - jar_xm_get_position(musicChannels_g[index].xmctx, NULL, NULL, NULL, &samples); - secondsPlayed = (float)samples / (48000.f * musicChannels_g[index].mixc->channels); // Not sure if this is the correct value + jar_xm_get_position(musicStreams[index].xmctx, NULL, NULL, NULL, &samples); + secondsPlayed = (float)samples/(48000.0f*musicStreams[index].mixc->channels); // Not sure if this is the correct value } - else if(musicChannels_g[index].chipTune && musicChannels_g[index].modctx.mod_loaded) + else if(musicStreams[index].chipTune && musicStreams[index].modctx.mod_loaded) { - long numsamp = jar_mod_current_samples(&musicChannels_g[index].modctx); - secondsPlayed = (float)numsamp / (48000.f); + long numsamp = jar_mod_current_samples(&musicStreams[index].modctx); + secondsPlayed = (float)numsamp/(48000.0f); } else { - int totalSamples = stb_vorbis_stream_length_in_samples(musicChannels_g[index].stream) * musicChannels_g[index].mixc->channels; - int samplesPlayed = totalSamples - musicChannels_g[index].totalSamplesLeft; - secondsPlayed = (float)samplesPlayed / (musicChannels_g[index].mixc->sampleRate * musicChannels_g[index].mixc->channels); + int totalSamples = stb_vorbis_stream_length_in_samples(musicStreams[index].stream)*musicStreams[index].mixc->channels; + int samplesPlayed = totalSamples - musicStreams[index].totalSamplesLeft; + secondsPlayed = (float)samplesPlayed/(musicStreams[index].mixc->sampleRate*musicStreams[index].mixc->channels); } } @@ -1076,30 +1141,30 @@ static bool BufferMusicStream(int index, int numBuffers) int size = 0; // Total size of data steamed in L+R samples for xm floats, individual L or R for ogg shorts bool active = true; // We can get more data from stream (not finished) - if (musicChannels_g[index].chipTune) // There is no end of stream for xmfiles, once the end is reached zeros are generated for non looped chiptunes. + if (musicStreams[index].chipTune) // There is no end of stream for xmfiles, once the end is reached zeros are generated for non looped chiptunes. { for (int i = 0; i < numBuffers; i++) { - if (musicChannels_g[index].modctx.mod_loaded) + if (musicStreams[index].modctx.mod_loaded) { - if (musicChannels_g[index].totalSamplesLeft >= MUSIC_BUFFER_SIZE_SHORT) size = MUSIC_BUFFER_SIZE_SHORT/2; - else size = musicChannels_g[index].totalSamplesLeft/2; + if (musicStreams[index].totalSamplesLeft >= MUSIC_BUFFER_SIZE_SHORT) size = MUSIC_BUFFER_SIZE_SHORT/2; + else size = musicStreams[index].totalSamplesLeft/2; - jar_mod_fillbuffer(&musicChannels_g[index].modctx, pcm, size, 0 ); - BufferMixChannel(musicChannels_g[index].mixc, pcm, size*2); + jar_mod_fillbuffer(&musicStreams[index].modctx, pcm, size, 0 ); + BufferMixChannel(musicStreams[index].mixc, pcm, size*2); } - else if (musicChannels_g[index].xmctx) + else if (musicStreams[index].xmctx) { - if (musicChannels_g[index].totalSamplesLeft >= MUSIC_BUFFER_SIZE_FLOAT) size = MUSIC_BUFFER_SIZE_FLOAT/2; - else size = musicChannels_g[index].totalSamplesLeft/2; + if (musicStreams[index].totalSamplesLeft >= MUSIC_BUFFER_SIZE_FLOAT) size = MUSIC_BUFFER_SIZE_FLOAT/2; + else size = musicStreams[index].totalSamplesLeft/2; - jar_xm_generate_samples(musicChannels_g[index].xmctx, pcmf, size); // reads 2*readlen shorts and moves them to buffer+size memory location - BufferMixChannel(musicChannels_g[index].mixc, pcmf, size*2); + jar_xm_generate_samples(musicStreams[index].xmctx, pcmf, size); // reads 2*readlen shorts and moves them to buffer+size memory location + BufferMixChannel(musicStreams[index].mixc, pcmf, size*2); } - musicChannels_g[index].totalSamplesLeft -= size; + musicStreams[index].totalSamplesLeft -= size; - if (musicChannels_g[index].totalSamplesLeft <= 0) + if (musicStreams[index].totalSamplesLeft <= 0) { active = false; break; @@ -1108,16 +1173,16 @@ static bool BufferMusicStream(int index, int numBuffers) } else { - if (musicChannels_g[index].totalSamplesLeft >= MUSIC_BUFFER_SIZE_SHORT) size = MUSIC_BUFFER_SIZE_SHORT; - else size = musicChannels_g[index].totalSamplesLeft; + if (musicStreams[index].totalSamplesLeft >= MUSIC_BUFFER_SIZE_SHORT) size = MUSIC_BUFFER_SIZE_SHORT; + else size = musicStreams[index].totalSamplesLeft; for (int i = 0; i < numBuffers; i++) { - int streamedBytes = stb_vorbis_get_samples_short_interleaved(musicChannels_g[index].stream, musicChannels_g[index].mixc->channels, pcm, size); - BufferMixChannel(musicChannels_g[index].mixc, pcm, streamedBytes * musicChannels_g[index].mixc->channels); - musicChannels_g[index].totalSamplesLeft -= streamedBytes * musicChannels_g[index].mixc->channels; + int streamedBytes = stb_vorbis_get_samples_short_interleaved(musicStreams[index].stream, musicStreams[index].mixc->channels, pcm, size); + BufferMixChannel(musicStreams[index].mixc, pcm, streamedBytes * musicStreams[index].mixc->channels); + musicStreams[index].totalSamplesLeft -= streamedBytes * musicStreams[index].mixc->channels; - if (musicChannels_g[index].totalSamplesLeft <= 0) + if (musicStreams[index].totalSamplesLeft <= 0) { active = false; break; @@ -1134,63 +1199,16 @@ static void EmptyMusicStream(int index) ALuint buffer = 0; int queued = 0; - alGetSourcei(musicChannels_g[index].mixc->alSource, AL_BUFFERS_QUEUED, &queued); + alGetSourcei(musicStreams[index].mixc->alSource, AL_BUFFERS_QUEUED, &queued); while (queued > 0) { - alSourceUnqueueBuffers(musicChannels_g[index].mixc->alSource, 1, &buffer); + alSourceUnqueueBuffers(musicStreams[index].mixc->alSource, 1, &buffer); queued--; } } -// Determine if a music stream is ready to be written -static int IsMusicStreamReadyForBuffering(int index) -{ - ALint processed = 0; - alGetSourcei(musicChannels_g[index].mixc->alSource, AL_BUFFERS_PROCESSED, &processed); - return processed; -} - -// Update (re-fill) music buffers if data already processed -void UpdateMusicStream(int index) -{ - ALenum state; - bool active = true; - int numBuffers = IsMusicStreamReadyForBuffering(index); - - if (musicChannels_g[index].mixc->playing && (index < MAX_MUSIC_STREAMS) && musicChannels_g[index].enabled && musicChannels_g[index].mixc && numBuffers) - { - active = BufferMusicStream(index, numBuffers); - - if (!active && musicChannels_g[index].loop) - { - if (musicChannels_g[index].chipTune) - { - if(musicChannels_g[index].modctx.mod_loaded) jar_mod_seek_start(&musicChannels_g[index].modctx); - - musicChannels_g[index].totalSamplesLeft = musicChannels_g[index].totalLengthSeconds * 48000.f; - } - else - { - stb_vorbis_seek_start(musicChannels_g[index].stream); - musicChannels_g[index].totalSamplesLeft = stb_vorbis_stream_length_in_samples(musicChannels_g[index].stream) * musicChannels_g[index].mixc->channels; - } - - active = BufferMusicStream(index, IsMusicStreamReadyForBuffering(index)); - } - - if (alGetError() != AL_NO_ERROR) TraceLog(WARNING, "Error buffering data..."); - - alGetSourcei(musicChannels_g[index].mixc->alSource, AL_SOURCE_STATE, &state); - - if (state != AL_PLAYING && active) alSourcePlay(musicChannels_g[index].mixc->alSource); - - if (!active) StopMusicStream(index); - - } -} - // Load WAV file into Wave structure static Wave LoadWAV(const char *fileName) { diff --git a/src/audio.h b/src/audio.h index fe72d866..b6850911 100644 --- a/src/audio.h +++ b/src/audio.h @@ -2,13 +2,26 @@ * * raylib.audio * -* Basic functions to manage Audio: InitAudioDevice, LoadAudioFiles, PlayAudioFiles +* Basic functions to manage Audio: +* Manage audio device (init/close) +* Load and Unload audio files +* Play/Stop/Pause/Resume loaded audio +* Manage mixing channels +* Manage raw audio context * * Uses external lib: * OpenAL Soft - Audio device management lib (http://kcat.strangesoft.net/openal.html) * stb_vorbis - Ogg audio files loading (http://www.nothings.org/stb_vorbis/) +* jar_xm - XM module file loading +* jar_mod - MOD audio file loading * -* Copyright (c) 2015 Ramon Santamaria (@raysan5) +* Many thanks to Joshua Reisenauer (github: @kd7tck) for the following additions: +* XM audio module support (jar_xm) +* MOD audio module support (jar_mod) +* Mixing channels support +* Raw audio context support +* +* Copyright (c) 2014-2016 Ramon Santamaria (@raysan5) * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. @@ -49,23 +62,19 @@ // Sound source type typedef struct Sound { - unsigned int source; - unsigned int buffer; - AudioError error; // if there was any error during the creation or use of this Sound + unsigned int source; // Sound audio source id + unsigned int buffer; // Sound audio buffer id } Sound; // Wave type, defines audio wave data typedef struct Wave { void *data; // Buffer data pointer unsigned int dataSize; // Data size in bytes - unsigned int sampleRate; - short bitsPerSample; + unsigned int sampleRate; // Samples per second to be played + short bitsPerSample; // Sample size in bits short channels; } Wave; -typedef int RawAudioContext; - - #ifdef __cplusplus extern "C" { // Prevents name mangling of functions #endif @@ -80,7 +89,7 @@ extern "C" { // Prevents name mangling of functions //---------------------------------------------------------------------------------- void InitAudioDevice(void); // Initialize audio device and context void CloseAudioDevice(void); // Close the audio device and context (and music stream) -bool IsAudioDeviceReady(void); // True if call to InitAudioDevice() was successful and CloseAudioDevice() has not been called yet +bool IsAudioDeviceReady(void); // Check if device has been initialized successfully Sound LoadSound(char *fileName); // Load sound to memory Sound LoadSoundFromWave(Wave wave); // Load sound to memory from wave data @@ -100,17 +109,14 @@ void PauseMusicStream(int index); // Pause music p void ResumeMusicStream(int index); // Resume playing paused music bool IsMusicPlaying(int index); // Check if music is playing void SetMusicVolume(int index, float volume); // Set volume for music (1.0 is max level) +void SetMusicPitch(int index, float pitch); // Set pitch for a music (1.0 is base level) float GetMusicTimeLength(int index); // Get music time length (in seconds) float GetMusicTimePlayed(int index); // Get current music time played (in seconds) -int GetMusicStreamCount(void); -void SetMusicPitch(int index, float pitch); - -// used to output raw audio streams, returns negative numbers on error -// if floating point is false the data size is 16bit short, otherwise it is float 32bit -RawAudioContext InitRawAudioContext(int sampleRate, int channels, bool floatingPoint); +int GetMusicStreamCount(void); // Get number of streams loaded -void CloseRawAudioContext(RawAudioContext ctx); -int BufferRawAudioContext(RawAudioContext ctx, void *data, unsigned short numberElements); // returns number of elements buffered +int InitRawMixChannel(int sampleRate, int channels, bool floatingPoint); // Initialize raw audio mix channel for audio buffering +int BufferRawMixChannel(int mixc, void *data, unsigned short numberElements); // Buffers data directly to raw mix channel +void CloseRawMixChannel(int mixc); // Closes and frees raw mix channel #ifdef __cplusplus } @@ -181,11 +181,10 @@ static uint64_t baseTime; // Base time measure for hi-res timer static bool windowShouldClose = false; // Flag to set window for closing #endif -static unsigned int displayWidth, displayHeight; // Display width and height (monitor, device-screen, LCD, ...) +// Display size-related data +static unsigned int displayWidth, displayHeight; // Display width and height (monitor, device-screen, LCD, ...) static int screenWidth, screenHeight; // Screen width and height (used render area) -static int renderWidth, renderHeight; // Framebuffer width and height (render area) - // NOTE: Framebuffer could include black bars - +static int renderWidth, renderHeight; // Framebuffer width and height (render area, including black bars if required) static int renderOffsetX = 0; // Offset X from render area (must be divided by 2) static int renderOffsetY = 0; // Offset Y from render area (must be divided by 2) static bool fullscreen = false; // Fullscreen mode (useful only for PLATFORM_DESKTOP) @@ -214,7 +213,7 @@ static bool cursorHidden; // Track if cursor is hidden #endif static Vector2 mousePosition; // Mouse position on screen -static Vector2 touchPosition[MAX_TOUCH_POINTS]; // Touch position on screen +static Vector2 touchPosition[MAX_TOUCH_POINTS]; // Touch position on screen #if defined(PLATFORM_DESKTOP) static char **dropFilesPath; // Store dropped files paths as strings @@ -226,7 +225,7 @@ static double updateTime, drawTime; // Time measures for update and draw static double frameTime; // Time measure for one frame static double targetTime = 0.0; // Desired time for one frame, if 0 not applied -static char configFlags = 0; // Configuration flags (bit based) +static char configFlags = 0; // Configuration flags (bit based) static bool showLogo = false; // Track if showing logo at init is enabled //---------------------------------------------------------------------------------- diff --git a/src/raylib.h b/src/raylib.h index 227f83f2..e3a17ebb 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -431,8 +431,8 @@ typedef struct Model { // Light type typedef struct LightData { unsigned int id; // Light unique id - int type; // Light type: LIGHT_POINT, LIGHT_DIRECTIONAL, LIGHT_SPOT bool enabled; // Light enabled + int type; // Light type: LIGHT_POINT, LIGHT_DIRECTIONAL, LIGHT_SPOT Vector3 position; // Light position Vector3 target; // Light target: LIGHT_DIRECTIONAL and LIGHT_SPOT (cone direction target) @@ -468,8 +468,6 @@ typedef struct Wave { short channels; } Wave; -typedef int RawAudioContext; - // Texture formats // NOTE: Support depends on OpenGL version and platform typedef enum { @@ -895,17 +893,10 @@ void PauseMusicStream(int index); // Pause music p void ResumeMusicStream(int index); // Resume playing paused music bool IsMusicPlaying(int index); // Check if music is playing void SetMusicVolume(int index, float volume); // Set volume for music (1.0 is max level) +void SetMusicPitch(int index, float pitch); // Set pitch for a music (1.0 is base level) float GetMusicTimeLength(int index); // Get current music time length (in seconds) float GetMusicTimePlayed(int index); // Get current music time played (in seconds) -int GetMusicStreamCount(void); -void SetMusicPitch(int index, float pitch); - -// used to output raw audio streams, returns negative numbers on error -// if floating point is false the data size is 16bit short, otherwise it is float 32bit -RawAudioContext InitRawAudioContext(int sampleRate, int channels, bool floatingPoint); - -void CloseRawAudioContext(RawAudioContext ctx); -int BufferRawAudioContext(RawAudioContext ctx, void *data, unsigned short numberElements); // returns number of elements buffered +int GetMusicStreamCount(void); // Get number of streams loaded #ifdef __cplusplus } @@ -31,7 +31,7 @@ #include <stdio.h> // Required for: fopen(), fclose(), fread()... [Used only on ReadTextFile()] #include <stdlib.h> // Required for: malloc(), free(), rand() #include <string.h> // Required for: strcmp(), strlen(), strtok() -#include <math.h> // Required for: atan() +#include <math.h> // Required for: atan2() #ifndef RLGL_STANDALONE #include "raymath.h" // Required for Vector3 and Matrix functions @@ -54,12 +54,11 @@ #include <OpenGL/gl3.h> // OpenGL 3 library for OSX #else #define GLAD_IMPLEMENTATION -#if defined(RLGL_STANDALONE) - #include "glad.h" // GLAD extensions loading library, includes OpenGL headers -#else - #include "external/glad.h" // GLAD extensions loading library, includes OpenGL headers -#endif - + #if defined(RLGL_STANDALONE) + #include "glad.h" // GLAD extensions loading library, includes OpenGL headers + #else + #include "external/glad.h" // GLAD extensions loading library, includes OpenGL headers + #endif #endif #endif @@ -74,7 +73,11 @@ #endif #if !defined(GRAPHICS_API_OPENGL_11) && !defined(RLGL_NO_STANDARD_SHADER) - #include "standard_shader.h" // Standard shader to embed + #include "shader_standard.h" // Standard shader to be embedded +#endif + +#if !defined(GRAPHICS_API_OPENGL_11) && !defined(RLGL_NO_DISTORTION_SHADER) + #include "shader_distortion.h" // Distortion shader to be embedded #endif //#define RLGL_OCULUS_SUPPORT // Enable Oculus Rift code @@ -82,6 +85,12 @@ #include "external/OculusSDK/LibOVR/Include/OVR_CAPI_GL.h" // Oculus SDK for OpenGL #endif +#if defined(RLGL_STANDALONE) + #define OCULUSAPI +#else + #define OCULUSAPI static +#endif + //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- @@ -188,6 +197,29 @@ typedef struct { //Guint fboId; } DrawCall; +// Head-Mounted-Display device parameters +typedef struct { + int hResolution; // HMD horizontal resolution in pixels + int vResolution; // HMD vertical resolution in pixels + float hScreenSize; // HMD horizontal size in meters + float vScreenSize; // HMD vertical size in meters + float vScreenCenter; // HMD screen center in meters + float eyeToScreenDistance; // HMD distance between eye and display in meters + float lensSeparationDistance; // HMD lens separation distance in meters + float interpupillaryDistance; // HMD IPD (distance between pupils) in meters + float distortionK[4]; // HMD lens distortion constant parameters + float chromaAbCorrection[4]; // HMD chromatic aberration correction parameters +} VrDeviceInfo; + +// VR Stereo rendering configuration for simulator +typedef struct { + RenderTexture2D stereoFbo; // VR stereo rendering framebuffer + Shader distortionShader; // VR stereo rendering distortion shader + //Rectangle eyesViewport[2]; // VR stereo rendering eyes viewports + Matrix eyesProjection[2]; // VR stereo rendering eyes projection matrices + Matrix eyesViewOffset[2]; // VR stereo rendering eyes view offset matrices +} VrStereoConfig; + #if defined(RLGL_OCULUS_SUPPORT) typedef struct OculusBuffer { ovrTextureSwapChain textureChain; @@ -230,9 +262,9 @@ static DrawMode currentDrawMode; static float currentDepth = -1.0f; -static DynamicBuffer lines; -static DynamicBuffer triangles; -static DynamicBuffer quads; +static DynamicBuffer lines; // Default dynamic buffer for lines data +static DynamicBuffer triangles; // Default dynamic buffer for triangles data +static DynamicBuffer quads; // Default dynamic buffer for quads data (used to draw textures) // Default buffers draw calls static DrawCall *draws; @@ -244,9 +276,10 @@ static int tempBufferCount = 0; static bool useTempBuffer = false; // Shader Programs -static Shader defaultShader; -static Shader standardShader; // Lazy initialization when GetStandardShader() -static Shader currentShader; // By default, defaultShader +static Shader defaultShader; // Basic shader, support vertex color and diffuse texture +static Shader standardShader; // Shader with support for lighting and materials + // NOTE: Lazy initialization when GetStandardShader() +static Shader currentShader; // Shader to be used on rendering (by default, defaultShader) static bool standardShaderLoaded = false; // Flag to track if standard shader has been loaded // Flags for supported extensions @@ -257,10 +290,6 @@ static bool texCompETC1Supported = false; // ETC1 texture compression support static bool texCompETC2Supported = false; // ETC2/EAC texture compression support static bool texCompPVRTSupported = false; // PVR texture compression support static bool texCompASTCSupported = false; // ASTC texture compression support - -// Lighting data -static Light lights[MAX_LIGHTS]; // Lights pool -static int lightsCount; // Counts current enabled physic objects #endif #if defined(RLGL_OCULUS_SUPPORT) @@ -274,17 +303,14 @@ static OculusMirror mirror; // Oculus mirror texture and fbo static unsigned int frameIndex = 0; // Oculus frames counter, used to discard frames from chain #endif -static bool oculusReady = false; // Oculus device ready flag -static bool oculusSimulator = false; // Oculus device simulator -static bool vrEnabled = false; // VR experience enabled (Oculus device or simulator) -static bool vrControl = true; // VR controlled by user code, instead of internally - -static RenderTexture2D stereoFbo; -static Shader distortionShader; - -// Compressed textures support flags -static bool texCompDXTSupported = false; // DDS texture compression support -static bool npotSupported = false; // NPOT textures full support +// VR global variables +static VrDeviceInfo hmd; // Current VR device info +static VrStereoConfig vrConfig; // VR stereo configuration for simulator +static bool vrDeviceReady = false; // VR device ready flag +static bool vrSimulator = false; // VR simulator enabled flag +static bool vrEnabled = false; // VR experience enabled (device or simulator) +static bool vrRendering = true; // VR stereo rendering enabled/disabled flag + // NOTE: This flag is useful to render data over stereo image (i.e. FPS) #if defined(GRAPHICS_API_OPENGL_ES2) // NOTE: VAO functionality is exposed through extensions (OES) @@ -294,6 +320,10 @@ static PFNGLDELETEVERTEXARRAYSOESPROC glDeleteVertexArrays; //static PFNGLISVERTEXARRAYOESPROC glIsVertexArray; // NOTE: Fails in WebGL, omitted #endif +// Compressed textures support flags +static bool texCompDXTSupported = false; // DDS texture compression support +static bool npotSupported = false; // NPOT textures full support + static int blendMode = 0; // Track current blending mode // White texture useful for plain color polys (required by shader) @@ -303,6 +333,10 @@ static unsigned int whiteTexture; static int screenWidth; // Default framebuffer width static int screenHeight; // Default framebuffer height +// Lighting data +static Light lights[MAX_LIGHTS]; // Lights pool +static int lightsCount = 0; // Enabled lights counter + //---------------------------------------------------------------------------------- // Module specific Functions Declaration //---------------------------------------------------------------------------------- @@ -321,15 +355,26 @@ static void UpdateDefaultBuffers(void); // Update default internal buffers ( static void DrawDefaultBuffers(int eyesCount); // Draw default internal buffers vertex data static void UnloadDefaultBuffers(void); // Unload default internal buffers vertex data from CPU and GPU +// Configure stereo rendering (including distortion shader) with HMD device parameters +static void SetStereoConfig(VrDeviceInfo info); + // Set internal projection and modelview matrix depending on eyes tracking data static void SetStereoView(int eye, Matrix matProjection, Matrix matModelView); static void SetShaderLights(Shader shader); // Sets shader uniform values for lights array -static char *ReadTextFile(const char *fileName); +static char *ReadTextFile(const char *fileName); // Read chars array from text file #endif #if defined(RLGL_OCULUS_SUPPORT) +#if !defined(RLGL_STANDALONE) +static bool InitOculusDevice(void); // Initialize Oculus device (returns true if success) +static void CloseOculusDevice(void); // Close Oculus device +static void UpdateOculusTracking(void); // Update Oculus head position-orientation tracking +static void BeginOculusDrawing(void); // Setup Oculus buffers for drawing +static void EndOculusDrawing(void); // Finish Oculus drawing and blit framebuffer to mirror +#endif + static OculusBuffer LoadOculusBuffer(ovrSession session, int width, int height); // Load Oculus required buffers static void UnloadOculusBuffer(ovrSession session, OculusBuffer buffer); // Unload texture required buffers static OculusMirror LoadOculusMirror(ovrSession session, int width, int height); // Load Oculus mirror buffers @@ -339,6 +384,8 @@ static OculusLayer InitOculusLayer(ovrSession session); static Matrix FromOvrMatrix(ovrMatrix4f ovrM); // Convert from Oculus ovrMatrix4f struct to raymath Matrix struct #endif + + #if defined(GRAPHICS_API_OPENGL_11) static int GenerateMipmaps(unsigned char *data, int baseWidth, int baseHeight); static Color *GenNextMipmap(Color *srcData, int srcWidth, int srcHeight); @@ -1216,7 +1263,7 @@ void rlglDraw(void) // NOTE: Default buffers upload and draw UpdateDefaultBuffers(); - if (vrEnabled && vrControl) DrawDefaultBuffers(2); + if (vrEnabled && vrRendering) DrawDefaultBuffers(2); else DrawDefaultBuffers(1); #endif } @@ -1849,6 +1896,8 @@ void rlglDrawMesh(Mesh mesh, Material material, Matrix transform) glTexCoordPointer(2, GL_FLOAT, 0, mesh.texcoords); // Pointer to texture coords array if (mesh.normals != NULL) glNormalPointer(GL_FLOAT, 0, mesh.normals); // Pointer to normals array if (mesh.colors != NULL) glColorPointer(4, GL_UNSIGNED_BYTE, 0, mesh.colors); // Pointer to colors array + + // TODO: Support OpenGL 1.1 lighting system rlPushMatrix(); rlMultMatrixf(MatrixToFloat(transform)); @@ -2453,27 +2502,36 @@ Light CreateLight(int type, Vector3 position, Color diffuse) { Light light = NULL; -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // Allocate dynamic memory - light = (Light)malloc(sizeof(LightData)); - - // Initialize light values with generic values - light->id = lightsCount; - light->type = type; - light->enabled = true; - - light->position = position; - light->target = (Vector3){ 0.0f, 0.0f, 0.0f }; - light->intensity = 1.0f; - light->diffuse = diffuse; - - // Add new light to the array - lights[lightsCount] = light; - - // Increase enabled lights count - lightsCount++; -#else - // TODO: Support OpenGL 1.1 lighting system + if (lightsCount < MAX_LIGHTS) + { + // Allocate dynamic memory + light = (Light)malloc(sizeof(LightData)); + + // Initialize light values with generic values + light->id = lightsCount; + light->type = type; + light->enabled = true; + + light->position = position; + light->target = (Vector3){ 0.0f, 0.0f, 0.0f }; + light->intensity = 1.0f; + light->diffuse = diffuse; + + // Add new light to the array + lights[lightsCount] = light; + + // Increase enabled lights count + lightsCount++; + } + else + { + TraceLog(WARNING, "Too many lights, only supported up to %i lights", MAX_LIGHTS); + + // NOTE: Returning latest created light to avoid crashes + light = lights[lightsCount]; + } + +#if defined(GRAPHICS_API_OPENGL_11) TraceLog(WARNING, "Lighting currently not supported on OpenGL 1.1"); #endif @@ -2483,157 +2541,170 @@ Light CreateLight(int type, Vector3 position, Color diffuse) // Destroy a light and take it out of the list void DestroyLight(Light light) { -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // Free dynamic memory allocation - free(lights[light->id]); - - // Remove *obj from the pointers array - for (int i = light->id; i < lightsCount; i++) + if (light != NULL) { - // Resort all the following pointers of the array - if ((i + 1) < lightsCount) + // Free dynamic memory allocation + free(lights[light->id]); + + // Remove *obj from the pointers array + for (int i = light->id; i < lightsCount; i++) { - lights[i] = lights[i + 1]; - lights[i]->id = lights[i + 1]->id; + // Resort all the following pointers of the array + if ((i + 1) < lightsCount) + { + lights[i] = lights[i + 1]; + lights[i]->id = lights[i + 1]->id; + } + else free(lights[i]); } - else free(lights[i]); + + // Decrease enabled physic objects count + lightsCount--; } - - // Decrease enabled physic objects count - lightsCount--; -#endif } // Init VR device (or simulator) // NOTE: If device is not available, it fallbacks to default device (simulator) +// NOTE: It modifies the global variable: VrDeviceInfo hmd void InitVrDevice(int hmdDevice) { -#if defined(RLGL_OCULUS_SUPPORT) - // Initialize Oculus device - ovrResult result = ovr_Initialize(NULL); - if (OVR_FAILURE(result)) - { - TraceLog(WARNING, "OVR: Could not initialize Oculus device"); - oculusReady = false; - } - else +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + switch (hmdDevice) { - result = ovr_Create(&session, &luid); - if (OVR_FAILURE(result)) + case HMD_DEFAULT_DEVICE: TraceLog(INFO, "Initializing default VR Device (Oculus Rift CV1)"); + case HMD_OCULUS_RIFT_DK2: + case HMD_OCULUS_RIFT_CV1: { - TraceLog(WARNING, "OVR: Could not create Oculus session"); - ovr_Shutdown(); - oculusReady = false; - } - else - { - hmdDesc = ovr_GetHmdDesc(session); - - TraceLog(INFO, "OVR: Product Name: %s", hmdDesc.ProductName); - TraceLog(INFO, "OVR: Manufacturer: %s", hmdDesc.Manufacturer); - TraceLog(INFO, "OVR: Product ID: %i", hmdDesc.ProductId); - TraceLog(INFO, "OVR: Product Type: %i", hmdDesc.Type); - //TraceLog(INFO, "OVR: Serial Number: %s", hmdDesc.SerialNumber); - TraceLog(INFO, "OVR: Resolution: %ix%i", hmdDesc.Resolution.w, hmdDesc.Resolution.h); - - // NOTE: Oculus mirror is set to defined screenWidth and screenHeight... - // ...ideally, it should be (hmdDesc.Resolution.w/2, hmdDesc.Resolution.h/2) - - // Initialize Oculus Buffers - layer = InitOculusLayer(session); - buffer = LoadOculusBuffer(session, layer.width, layer.height); - mirror = LoadOculusMirror(session, hmdDesc.Resolution.w/2, hmdDesc.Resolution.h/2); // NOTE: hardcoded... - layer.eyeLayer.ColorTexture[0] = buffer.textureChain; //SetOculusLayerTexture(eyeLayer, buffer.textureChain); - - // Recenter OVR tracking origin - ovr_RecenterTrackingOrigin(session); - - oculusReady = true; - vrEnabled = true; - } - } +#if defined(RLGL_OCULUS_SUPPORT) + vrDeviceReady = InitOculusDevice(); #else - oculusReady = false; + TraceLog(WARNING, "Oculus Rift not supported by default, recompile raylib with Oculus support"); #endif + } break; + case HMD_VALVE_HTC_VIVE: + case HMD_SAMSUNG_GEAR_VR: + case HMD_GOOGLE_CARDBOARD: + case HMD_SONY_PLAYSTATION_VR: + case HMD_RAZER_OSVR: + case HMD_FOVE_VR: TraceLog(WARNING, "VR Device not supported"); + default: break; + } - if (!oculusReady) + if (!vrDeviceReady) { - TraceLog(WARNING, "HMD Device not found: Initializing VR simulator"); + TraceLog(WARNING, "VR Device not found: Initializing VR Simulator (Oculus Rift CV1)"); + if (hmdDevice == HMD_OCULUS_RIFT_DK2) + { + // Oculus Rift DK2 parameters + hmd.hResolution = 1280; // HMD horizontal resolution in pixels + hmd.vResolution = 800; // HMD vertical resolution in pixels + hmd.hScreenSize = 0.14976f; // HMD horizontal size in meters + hmd.vScreenSize = 0.09356f; // HMD vertical size in meters + hmd.vScreenCenter = 0.04678f; // HMD screen center in meters + hmd.eyeToScreenDistance = 0.041f; // HMD distance between eye and display in meters + hmd.lensSeparationDistance = 0.0635f; // HMD lens separation distance in meters + hmd.interpupillaryDistance = 0.064f; // HMD IPD (distance between pupils) in meters + hmd.distortionK[0] = 1.0f; // HMD lens distortion constant parameter 0 + hmd.distortionK[1] = 0.22f; // HMD lens distortion constant parameter 1 + hmd.distortionK[2] = 0.24f; // HMD lens distortion constant parameter 2 + hmd.distortionK[3] = 0.0f; // HMD lens distortion constant parameter 3 + hmd.chromaAbCorrection[0] = 0.996f; // HMD chromatic aberration correction parameter 0 + hmd.chromaAbCorrection[1] = -0.004f; // HMD chromatic aberration correction parameter 1 + hmd.chromaAbCorrection[2] = 1.014f; // HMD chromatic aberration correction parameter 2 + hmd.chromaAbCorrection[3] = 0.0f; // HMD chromatic aberration correction parameter 3 + } + else if ((hmdDevice == HMD_DEFAULT_DEVICE) || (hmdDevice == HMD_OCULUS_RIFT_CV1)) + { + // Oculus Rift CV1 parameters + // NOTE: CV1 represents a complete HMD redesign compared to previous versions, + // new Fresnel-hybrid-asymmetric lenses have been added and, consequently, + // previous parameters (DK2) and distortion shader (DK2) doesn't work any more. + // I just defined a set of parameters for simulator that approximate to CV1 stereo rendering + // but result is not the same obtained with Oculus PC SDK. + hmd.hResolution = 2160; // HMD horizontal resolution in pixels + hmd.vResolution = 1200; // HMD vertical resolution in pixels + hmd.hScreenSize = 0.133793f; // HMD horizontal size in meters + hmd.vScreenSize = 0.0669; // HMD vertical size in meters + hmd.vScreenCenter = 0.04678f; // HMD screen center in meters + hmd.eyeToScreenDistance = 0.041f; // HMD distance between eye and display in meters + hmd.lensSeparationDistance = 0.07f; // HMD lens separation distance in meters + hmd.interpupillaryDistance = 0.07f; // HMD IPD (distance between pupils) in meters + hmd.distortionK[0] = 1.0f; // HMD lens distortion constant parameter 0 + hmd.distortionK[1] = 0.22f; // HMD lens distortion constant parameter 1 + hmd.distortionK[2] = 0.24f; // HMD lens distortion constant parameter 2 + hmd.distortionK[3] = 0.0f; // HMD lens distortion constant parameter 3 + hmd.chromaAbCorrection[0] = 0.996f; // HMD chromatic aberration correction parameter 0 + hmd.chromaAbCorrection[1] = -0.004f; // HMD chromatic aberration correction parameter 1 + hmd.chromaAbCorrection[2] = 1.014f; // HMD chromatic aberration correction parameter 2 + hmd.chromaAbCorrection[3] = 0.0f; // HMD chromatic aberration correction parameter 3 + } + // Initialize framebuffer and textures for stereo rendering - stereoFbo = rlglLoadRenderTexture(screenWidth, screenHeight); + // NOTE: screen size should match HMD aspect ratio + vrConfig.stereoFbo = rlglLoadRenderTexture(screenWidth, screenHeight); - // Load oculus-distortion shader (oculus parameters setup internally) - // TODO: Embed coulus distortion shader (in this function like default shader?) - distortionShader = LoadShader("resources/shaders/glsl330/base.vs", "resources/shaders/glsl330/distortion.fs"); + // Load distortion shader (initialized by default with Oculus Rift CV1 parameters) + vrConfig.distortionShader.id = LoadShaderProgram(vDistortionShaderStr, fDistortionShaderStr); + if (vrConfig.distortionShader.id != 0) LoadDefaultShaderLocations(&vrConfig.distortionShader); + + SetStereoConfig(hmd); - oculusSimulator = true; + vrSimulator = true; vrEnabled = true; } +#endif + +#if defined(GRAPHICS_API_OPENGL_11) + TraceLog(WARNING, "VR device or simulator not supported on OpenGL 1.1"); +#endif } // Close VR device (or simulator) void CloseVrDevice(void) { +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) #if defined(RLGL_OCULUS_SUPPORT) - if (oculusReady) - { - UnloadOculusMirror(session, mirror); // Unload Oculus mirror buffer - UnloadOculusBuffer(session, buffer); // Unload Oculus texture buffers - - ovr_Destroy(session); // Free Oculus session data - ovr_Shutdown(); // Close Oculus device connection - } + if (vrDeviceReady) CloseOculusDevice(); else #endif { - // Unload stereo framebuffer and texture - rlDeleteRenderTextures(stereoFbo); - - // Unload oculus-distortion shader - UnloadShader(distortionShader); + rlDeleteRenderTextures(vrConfig.stereoFbo); // Unload stereo framebuffer and texture + UnloadShader(vrConfig.distortionShader); // Unload distortion shader } - - oculusReady = false; +#endif + vrDeviceReady = false; } // Detect if VR device is available bool IsVrDeviceReady(void) { - return (oculusReady || oculusSimulator) && vrEnabled; + return (vrDeviceReady || vrSimulator) && vrEnabled; } // Enable/Disable VR experience (device or simulator) void ToggleVrMode(void) { - vrEnabled = !vrEnabled; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (vrDeviceReady || vrSimulator) vrEnabled = !vrEnabled; + else vrEnabled = false; + + if (!vrEnabled) + { + // Reset viewport and default projection-modelview matrices + rlViewport(0, 0, screenWidth, screenHeight); + projection = MatrixOrtho(0, screenWidth, screenHeight, 0, 0.0f, 1.0f); + MatrixTranspose(&projection); + modelview = MatrixIdentity(); + } +#endif } // Update VR tracking (position and orientation) void UpdateVrTracking(void) { #if defined(RLGL_OCULUS_SUPPORT) - if (oculusReady) - { - frameIndex++; - - ovrPosef eyePoses[2]; - ovr_GetEyePoses(session, frameIndex, ovrTrue, layer.viewScaleDesc.HmdToEyeOffset, eyePoses, &layer.eyeLayer.SensorSampleTime); - - layer.eyeLayer.RenderPose[0] = eyePoses[0]; - layer.eyeLayer.RenderPose[1] = eyePoses[1]; - - // Get session status information - ovrSessionStatus sessionStatus; - ovr_GetSessionStatus(session, &sessionStatus); - - if (sessionStatus.ShouldQuit) TraceLog(WARNING, "OVR: Session should quit..."); - if (sessionStatus.ShouldRecenter) ovr_RecenterTrackingOrigin(session); - //if (sessionStatus.HmdPresent) // HMD is present. - //if (sessionStatus.DisplayLost) // HMD was unplugged or the display driver was manually disabled or encountered a TDR. - //if (sessionStatus.HmdMounted) // HMD is on the user's head. - //if (sessionStatus.IsVisible) // the game or experience has VR focus and is visible in the HMD. - } + if (vrDeviceReady) UpdateOculusTracking(); else #endif { @@ -2641,122 +2712,20 @@ void UpdateVrTracking(void) } } -// Set internal projection and modelview matrix depending on eyes tracking data -static void SetStereoView(int eye, Matrix matProjection, Matrix matModelView) -{ - if (vrEnabled) - { - Matrix eyeProjection = matProjection; - Matrix eyeModelView = matModelView; - -#if defined(RLGL_OCULUS_SUPPORT) - if (oculusReady) - { - rlViewport(layer.eyeLayer.Viewport[eye].Pos.x, layer.eyeLayer.Viewport[eye].Pos.y, - layer.eyeLayer.Viewport[eye].Size.w, layer.eyeLayer.Viewport[eye].Size.h); - - Quaternion eyeRenderPose = (Quaternion){ layer.eyeLayer.RenderPose[eye].Orientation.x, - layer.eyeLayer.RenderPose[eye].Orientation.y, - layer.eyeLayer.RenderPose[eye].Orientation.z, - layer.eyeLayer.RenderPose[eye].Orientation.w }; - QuaternionInvert(&eyeRenderPose); - Matrix eyeOrientation = QuaternionToMatrix(eyeRenderPose); - Matrix eyeTranslation = MatrixTranslate(-layer.eyeLayer.RenderPose[eye].Position.x, - -layer.eyeLayer.RenderPose[eye].Position.y, - -layer.eyeLayer.RenderPose[eye].Position.z); - - Matrix eyeView = MatrixMultiply(eyeTranslation, eyeOrientation); // Matrix containing eye-head movement - eyeModelView = MatrixMultiply(matModelView, eyeView); // Combine internal camera matrix (modelview) wih eye-head movement - - eyeProjection = layer.eyeProjections[eye]; - } - else -#endif - { - // Setup viewport and projection/modelview matrices using tracking data - rlViewport(eye*screenWidth/2, 0, screenWidth/2, screenHeight); - - static float IPD = 0.064f; // InterpupillaryDistance - float HScreenSize = 0.14976f; - float VScreenSize = 0.0936f; // HScreenSize/(1280.0f/800.0f) (DK2) - float VScreenCenter = 0.04675f; // VScreenSize/2 - float EyeToScreenDistance = 0.041f; - float LensSeparationDistance = 0.064f; //0.0635f (DK1) - - // NOTE: fovy value obtained from device parameters (Oculus Rift CV1) - float halfScreenDistance = VScreenSize/2.0f; - float fovy = 2.0f*atan(halfScreenDistance/EyeToScreenDistance)*RAD2DEG; - - float viewCenter = (float)HScreenSize*0.25f; - float eyeProjectionShift = viewCenter - LensSeparationDistance*0.5f; - float projectionCenterOffset = eyeProjectionShift/(float)HScreenSize; //4.0f*eyeProjectionShift/(float)HScreenSize; -/* - static float scale[2] = { 0.25, 0.45 }; - - if (IsKeyDown(KEY_RIGHT)) scale[0] += 0.01; - else if (IsKeyDown(KEY_LEFT)) scale[0] -= 0.01; - else if (IsKeyDown(KEY_UP)) scale[1] += 0.01; - else if (IsKeyDown(KEY_DOWN)) scale[1] -= 0.01; - - SetShaderValue(distortionShader, GetShaderLocation(distortionShader, "Scale"), scale, 2); - - if (IsKeyDown(KEY_N)) IPD += 0.02; - else if (IsKeyDown(KEY_M)) IPD -= 0.02; -*/ - // The matrixes for offsetting the projection and view for each eye, to achieve stereo effect - Vector3 projectionOffset = { -projectionCenterOffset, 0.0f, 0.0f }; - - // Camera movement might seem more natural if we model the head. - // Our axis of rotation is the base of our head, so we might want to add - // some y (base of head to eye level) and -z (center of head to eye protrusion) to the camera positions. - Vector3 viewOffset = { -IPD/2.0f, 0.075f, 0.045f }; - - // Negate the left eye versions - if (eye == 0) - { - projectionOffset.x *= -1.0f; - viewOffset.x *= -1.0f; - } - - // Adjust the view and projection matrixes - // View matrix is translated based on the eye offset - Matrix projCenter = MatrixPerspective(fovy, (double)((float)screenWidth*0.5f)/(double)screenHeight, 0.01, 1000.0); - - Matrix projTranslation = MatrixTranslate(projectionOffset.x, projectionOffset.y, projectionOffset.z); - Matrix viewTranslation = MatrixTranslate(viewOffset.x, viewOffset.y, viewOffset.z); - - eyeProjection = MatrixMultiply(projCenter, projTranslation); // projection - eyeModelView = MatrixMultiply(matModelView, viewTranslation); // modelview - - MatrixTranspose(&eyeProjection); - } - - SetMatrixModelview(eyeModelView); - SetMatrixProjection(eyeProjection); - } -} - // Begin Oculus drawing configuration void BeginVrDrawing(void) { +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) #if defined(RLGL_OCULUS_SUPPORT) - if (oculusReady) + if (vrDeviceReady) { - GLuint currentTexId; - int currentIndex; - - ovr_GetTextureSwapChainCurrentIndex(session, buffer.textureChain, ¤tIndex); - ovr_GetTextureSwapChainBufferGL(session, buffer.textureChain, currentIndex, ¤tTexId); - - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, buffer.fboId); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, currentTexId, 0); - //glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, buffer.depthId, 0); // Already binded + BeginOculusDrawing(); } else #endif { // Setup framebuffer for stereo rendering - rlEnableRenderTexture(stereoFbo.id); + rlEnableRenderTexture(vrConfig.stereoFbo.id); } // NOTE: If your application is configured to treat the texture as a linear format (e.g. GL_RGBA) @@ -2768,26 +2737,18 @@ void BeginVrDrawing(void) //glViewport(0, 0, buffer.width, buffer.height); // Useful if rendering to separate framebuffers (every eye) rlClearScreenBuffers(); // Clear current framebuffer(s) - vrControl = true; + vrRendering = true; +#endif } // End Oculus drawing process (and desktop mirror) void EndVrDrawing(void) { +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) #if defined(RLGL_OCULUS_SUPPORT) - if (oculusReady) + if (vrDeviceReady) { - // Unbind current framebuffer (Oculus buffer) - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - - ovr_CommitTextureSwapChain(session, buffer.textureChain); - - ovrLayerHeader *layers = &layer.eyeLayer.Header; - ovr_SubmitFrame(session, frameIndex, &layer.viewScaleDesc, &layers, 1); - - // Blit mirror texture to back buffer - BlitOculusMirror(session, mirror); + EndOculusDrawing(); } else #endif @@ -2808,9 +2769,9 @@ void EndVrDrawing(void) rlLoadIdentity(); // Reset internal modelview matrix // Draw RenderTexture (stereoFbo) using distortion shader - currentShader = distortionShader; + currentShader = vrConfig.distortionShader; - rlEnableTexture(stereoFbo.texture.id); + rlEnableTexture(vrConfig.stereoFbo.texture.id); rlPushMatrix(); rlBegin(RL_QUADS); @@ -2823,15 +2784,15 @@ void EndVrDrawing(void) // Bottom-right corner for texture and quad rlTexCoord2f(0.0f, 0.0f); - rlVertex2f(0.0f, stereoFbo.texture.height); + rlVertex2f(0.0f, vrConfig.stereoFbo.texture.height); // Top-right corner for texture and quad rlTexCoord2f(1.0f, 0.0f); - rlVertex2f(stereoFbo.texture.width, stereoFbo.texture.height); + rlVertex2f(vrConfig.stereoFbo.texture.width, vrConfig.stereoFbo.texture.height); // Top-left corner for texture and quad rlTexCoord2f(1.0f, 1.0f); - rlVertex2f(stereoFbo.texture.width, 0.0f); + rlVertex2f(vrConfig.stereoFbo.texture.width, 0.0f); rlEnd(); rlPopMatrix(); @@ -2845,7 +2806,8 @@ void EndVrDrawing(void) rlDisableDepthTest(); - vrControl = false; + vrRendering = false; +#endif } //---------------------------------------------------------------------------------- @@ -3625,72 +3587,79 @@ static void UnloadDefaultBuffers(void) // NOTE: It would be far easier with shader UBOs but are not supported on OpenGL ES 2.0f static void SetShaderLights(Shader shader) { - int locPoint = glGetUniformLocation(shader.id, "lightsCount"); - glUniform1i(locPoint, lightsCount); - + int locPoint = -1; char locName[32] = "lights[x].position\0"; - for (int i = 0; i < lightsCount; i++) + for (int i = 0; i < MAX_LIGHTS; i++) { locName[7] = '0' + i; - - memcpy(&locName[10], "enabled\0", strlen("enabled\0") + 1); - locPoint = GetShaderLocation(shader, locName); - glUniform1i(locPoint, lights[i]->enabled); - - memcpy(&locName[10], "type\0", strlen("type\0") + 1); - locPoint = GetShaderLocation(shader, locName); - glUniform1i(locPoint, lights[i]->type); - - memcpy(&locName[10], "diffuse\0", strlen("diffuse\0") + 2); - locPoint = glGetUniformLocation(shader.id, locName); - glUniform4f(locPoint, (float)lights[i]->diffuse.r/255, (float)lights[i]->diffuse.g/255, (float)lights[i]->diffuse.b/255, (float)lights[i]->diffuse.a/255); - - memcpy(&locName[10], "intensity\0", strlen("intensity\0")); - locPoint = glGetUniformLocation(shader.id, locName); - glUniform1f(locPoint, lights[i]->intensity); - - switch (lights[i]->type) + + if (lights[i] != NULL) // Only upload registered lights data { - case LIGHT_POINT: - { - memcpy(&locName[10], "position\0", strlen("position\0") + 1); - locPoint = GetShaderLocation(shader, locName); - glUniform3f(locPoint, lights[i]->position.x, lights[i]->position.y, lights[i]->position.z); - - memcpy(&locName[10], "radius\0", strlen("radius\0") + 2); - locPoint = GetShaderLocation(shader, locName); - glUniform1f(locPoint, lights[i]->radius); - } break; - case LIGHT_DIRECTIONAL: - { - memcpy(&locName[10], "direction\0", strlen("direction\0") + 2); - locPoint = GetShaderLocation(shader, locName); - Vector3 direction = { lights[i]->target.x - lights[i]->position.x, lights[i]->target.y - lights[i]->position.y, lights[i]->target.z - lights[i]->position.z }; - VectorNormalize(&direction); - glUniform3f(locPoint, direction.x, direction.y, direction.z); - } break; - case LIGHT_SPOT: + memcpy(&locName[10], "enabled\0", strlen("enabled\0") + 1); + locPoint = GetShaderLocation(shader, locName); + glUniform1i(locPoint, lights[i]->enabled); + + memcpy(&locName[10], "type\0", strlen("type\0") + 1); + locPoint = GetShaderLocation(shader, locName); + glUniform1i(locPoint, lights[i]->type); + + memcpy(&locName[10], "diffuse\0", strlen("diffuse\0") + 2); + locPoint = glGetUniformLocation(shader.id, locName); + glUniform4f(locPoint, (float)lights[i]->diffuse.r/255, (float)lights[i]->diffuse.g/255, (float)lights[i]->diffuse.b/255, (float)lights[i]->diffuse.a/255); + + memcpy(&locName[10], "intensity\0", strlen("intensity\0")); + locPoint = glGetUniformLocation(shader.id, locName); + glUniform1f(locPoint, lights[i]->intensity); + + switch (lights[i]->type) { - memcpy(&locName[10], "position\0", strlen("position\0") + 1); - locPoint = GetShaderLocation(shader, locName); - glUniform3f(locPoint, lights[i]->position.x, lights[i]->position.y, lights[i]->position.z); - - memcpy(&locName[10], "direction\0", strlen("direction\0") + 2); - locPoint = GetShaderLocation(shader, locName); - - Vector3 direction = { lights[i]->target.x - lights[i]->position.x, lights[i]->target.y - lights[i]->position.y, lights[i]->target.z - lights[i]->position.z }; - VectorNormalize(&direction); - glUniform3f(locPoint, direction.x, direction.y, direction.z); - - memcpy(&locName[10], "coneAngle\0", strlen("coneAngle\0")); - locPoint = GetShaderLocation(shader, locName); - glUniform1f(locPoint, lights[i]->coneAngle); - } break; - default: break; + case LIGHT_POINT: + { + memcpy(&locName[10], "position\0", strlen("position\0") + 1); + locPoint = GetShaderLocation(shader, locName); + glUniform3f(locPoint, lights[i]->position.x, lights[i]->position.y, lights[i]->position.z); + + memcpy(&locName[10], "radius\0", strlen("radius\0") + 2); + locPoint = GetShaderLocation(shader, locName); + glUniform1f(locPoint, lights[i]->radius); + } break; + case LIGHT_DIRECTIONAL: + { + memcpy(&locName[10], "direction\0", strlen("direction\0") + 2); + locPoint = GetShaderLocation(shader, locName); + Vector3 direction = { lights[i]->target.x - lights[i]->position.x, lights[i]->target.y - lights[i]->position.y, lights[i]->target.z - lights[i]->position.z }; + VectorNormalize(&direction); + glUniform3f(locPoint, direction.x, direction.y, direction.z); + } break; + case LIGHT_SPOT: + { + memcpy(&locName[10], "position\0", strlen("position\0") + 1); + locPoint = GetShaderLocation(shader, locName); + glUniform3f(locPoint, lights[i]->position.x, lights[i]->position.y, lights[i]->position.z); + + memcpy(&locName[10], "direction\0", strlen("direction\0") + 2); + locPoint = GetShaderLocation(shader, locName); + + Vector3 direction = { lights[i]->target.x - lights[i]->position.x, lights[i]->target.y - lights[i]->position.y, lights[i]->target.z - lights[i]->position.z }; + VectorNormalize(&direction); + glUniform3f(locPoint, direction.x, direction.y, direction.z); + + memcpy(&locName[10], "coneAngle\0", strlen("coneAngle\0")); + locPoint = GetShaderLocation(shader, locName); + glUniform1f(locPoint, lights[i]->coneAngle); + } break; + default: break; + } + + // TODO: Pass to the shader any other required data from LightData struct + } + else // Not enabled lights + { + memcpy(&locName[10], "enabled\0", strlen("enabled\0") + 1); + locPoint = GetShaderLocation(shader, locName); + glUniform1i(locPoint, 0); } - - // TODO: Pass to the shader any other required data from LightData struct } } @@ -3727,6 +3696,124 @@ static char *ReadTextFile(const char *fileName) return text; } + +// Configure stereo rendering (including distortion shader) with HMD device parameters +static void SetStereoConfig(VrDeviceInfo hmd) +{ + // Compute aspect ratio + float aspect = ((float)hmd.hResolution*0.5f)/(float)hmd.vResolution; + + // Compute lens parameters + float lensShift = (hmd.hScreenSize*0.25f - hmd.lensSeparationDistance*0.5f)/hmd.hScreenSize; + float leftLensCenter[2] = { 0.25 + lensShift, 0.5f }; + float rightLensCenter[2] = { 0.75 - lensShift, 0.5f }; + float leftScreenCenter[2] = { 0.25f, 0.5f }; + float rightScreenCenter[2] = { 0.75f, 0.5f }; + + // Compute distortion scale parameters + // NOTE: To get lens max radius, lensShift must be normalized to [-1..1] + float lensRadius = fabsf(-1.0f - 4.0f*lensShift); + float lensRadiusSq = lensRadius*lensRadius; + float distortionScale = hmd.distortionK[0] + + hmd.distortionK[1]*lensRadiusSq + + hmd.distortionK[2]*lensRadiusSq*lensRadiusSq + + hmd.distortionK[3]*lensRadiusSq*lensRadiusSq*lensRadiusSq; + + TraceLog(DEBUG, "VR: Distortion Scale: %f", distortionScale); + + float normScreenWidth = 0.5f; + float normScreenHeight = 1.0f; + float scaleIn[2] = { 2/normScreenWidth, 2/normScreenHeight/aspect }; + float scale[2] = { normScreenWidth*0.5/distortionScale, normScreenHeight*0.5*aspect/distortionScale }; + + TraceLog(DEBUG, "VR: Distortion Shader: LeftLensCenter = { %f, %f }", leftLensCenter[0], leftLensCenter[1]); + TraceLog(DEBUG, "VR: Distortion Shader: RightLensCenter = { %f, %f }", rightLensCenter[0], rightLensCenter[1]); + TraceLog(DEBUG, "VR: Distortion Shader: Scale = { %f, %f }", scale[0], scale[1]); + TraceLog(DEBUG, "VR: Distortion Shader: ScaleIn = { %f, %f }", scaleIn[0], scaleIn[1]); + + // Update distortion shader with lens and distortion-scale parameters + SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "leftLensCenter"), leftLensCenter, 2); + SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "rightLensCenter"), rightLensCenter, 2); + SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "leftScreenCenter"), leftScreenCenter, 2); + SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "rightScreenCenter"), rightScreenCenter, 2); + + SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "scale"), scale, 2); + SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "scaleIn"), scaleIn, 2); + SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "hmdWarpParam"), hmd.distortionK, 4); + SetShaderValue(vrConfig.distortionShader, GetShaderLocation(vrConfig.distortionShader, "chromaAbParam"), hmd.chromaAbCorrection, 4); + + // Fovy is normally computed with: 2*atan2(hmd.vScreenSize, 2*hmd.eyeToScreenDistance)*RAD2DEG + // ...but with lens distortion it is increased (see Oculus SDK Documentation) + //float fovy = 2.0f*atan2(hmd.vScreenSize*0.5f*distortionScale, hmd.eyeToScreenDistance)*RAD2DEG; // Really need distortionScale? + float fovy = 2.0f*atan2(hmd.vScreenSize*0.5f, hmd.eyeToScreenDistance)*RAD2DEG; + + // Compute camera projection matrices + float projOffset = 4.0f*lensShift; // Scaled to projection space coordinates [-1..1] + Matrix proj = MatrixPerspective(fovy, aspect, 0.01, 1000.0); + vrConfig.eyesProjection[0] = MatrixMultiply(proj, MatrixTranslate(projOffset, 0.0f, 0.0f)); + vrConfig.eyesProjection[1] = MatrixMultiply(proj, MatrixTranslate(-projOffset, 0.0f, 0.0f)); + + // NOTE: Projection matrices must be transposed due to raymath convention + MatrixTranspose(&vrConfig.eyesProjection[0]); + MatrixTranspose(&vrConfig.eyesProjection[1]); + + // Compute camera transformation matrices + // NOTE: Camera movement might seem more natural if we model the head. + // Our axis of rotation is the base of our head, so we might want to add + // some y (base of head to eye level) and -z (center of head to eye protrusion) to the camera positions. + vrConfig.eyesViewOffset[0] = MatrixTranslate(-hmd.interpupillaryDistance*0.5f, 0.075f, 0.045f); + vrConfig.eyesViewOffset[1] = MatrixTranslate(hmd.interpupillaryDistance*0.5f, 0.075f, 0.045f); + + // Compute eyes Viewports + //vrConfig.eyesViewport[0] = (Rectangle){ 0, 0, hmd.hResolution/2, hmd.vResolution }; + //vrConfig.eyesViewport[1] = (Rectangle){ hmd.hResolution/2, 0, hmd.hResolution/2, hmd.vResolution }; +} + +// Set internal projection and modelview matrix depending on eyes tracking data +static void SetStereoView(int eye, Matrix matProjection, Matrix matModelView) +{ + if (vrEnabled) + { + Matrix eyeProjection = matProjection; + Matrix eyeModelView = matModelView; + +#if defined(RLGL_OCULUS_SUPPORT) + if (vrDeviceReady) + { + rlViewport(layer.eyeLayer.Viewport[eye].Pos.x, layer.eyeLayer.Viewport[eye].Pos.y, + layer.eyeLayer.Viewport[eye].Size.w, layer.eyeLayer.Viewport[eye].Size.h); + + Quaternion eyeRenderPose = (Quaternion){ layer.eyeLayer.RenderPose[eye].Orientation.x, + layer.eyeLayer.RenderPose[eye].Orientation.y, + layer.eyeLayer.RenderPose[eye].Orientation.z, + layer.eyeLayer.RenderPose[eye].Orientation.w }; + QuaternionInvert(&eyeRenderPose); + Matrix eyeOrientation = QuaternionToMatrix(eyeRenderPose); + Matrix eyeTranslation = MatrixTranslate(-layer.eyeLayer.RenderPose[eye].Position.x, + -layer.eyeLayer.RenderPose[eye].Position.y, + -layer.eyeLayer.RenderPose[eye].Position.z); + + Matrix eyeView = MatrixMultiply(eyeTranslation, eyeOrientation); // Matrix containing eye-head movement + eyeModelView = MatrixMultiply(matModelView, eyeView); // Combine internal camera matrix (modelview) wih eye-head movement + + eyeProjection = layer.eyeProjections[eye]; + } + else +#endif + { + // Setup viewport and projection/modelview matrices using tracking data + rlViewport(eye*screenWidth/2, 0, screenWidth/2, screenHeight); + + // Apply view offset to modelview matrix + eyeModelView = MatrixMultiply(matModelView, vrConfig.eyesViewOffset[eye]); + + eyeProjection = vrConfig.eyesProjection[eye]; + } + + SetMatrixModelview(eyeModelView); + SetMatrixProjection(eyeProjection); + } +} #endif //defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) #if defined(GRAPHICS_API_OPENGL_11) @@ -3857,6 +3944,116 @@ static Color *GenNextMipmap(Color *srcData, int srcWidth, int srcHeight) #endif #if defined(RLGL_OCULUS_SUPPORT) +// Initialize Oculus device (returns true if success) +OCULUSAPI bool InitOculusDevice(void) +{ + bool oculusReady = false; + + ovrResult result = ovr_Initialize(NULL); + + if (OVR_FAILURE(result)) TraceLog(WARNING, "OVR: Could not initialize Oculus device"); + else + { + result = ovr_Create(&session, &luid); + if (OVR_FAILURE(result)) + { + TraceLog(WARNING, "OVR: Could not create Oculus session"); + ovr_Shutdown(); + } + else + { + hmdDesc = ovr_GetHmdDesc(session); + + TraceLog(INFO, "OVR: Product Name: %s", hmdDesc.ProductName); + TraceLog(INFO, "OVR: Manufacturer: %s", hmdDesc.Manufacturer); + TraceLog(INFO, "OVR: Product ID: %i", hmdDesc.ProductId); + TraceLog(INFO, "OVR: Product Type: %i", hmdDesc.Type); + //TraceLog(INFO, "OVR: Serial Number: %s", hmdDesc.SerialNumber); + TraceLog(INFO, "OVR: Resolution: %ix%i", hmdDesc.Resolution.w, hmdDesc.Resolution.h); + + // NOTE: Oculus mirror is set to defined screenWidth and screenHeight... + // ...ideally, it should be (hmdDesc.Resolution.w/2, hmdDesc.Resolution.h/2) + + // Initialize Oculus Buffers + layer = InitOculusLayer(session); + buffer = LoadOculusBuffer(session, layer.width, layer.height); + mirror = LoadOculusMirror(session, hmdDesc.Resolution.w/2, hmdDesc.Resolution.h/2); // NOTE: hardcoded... + layer.eyeLayer.ColorTexture[0] = buffer.textureChain; //SetOculusLayerTexture(eyeLayer, buffer.textureChain); + + // Recenter OVR tracking origin + ovr_RecenterTrackingOrigin(session); + + oculusReady = true; + vrEnabled = true; + } + } + + return oculusReady; +} + +// Close Oculus device (and unload buffers) +OCULUSAPI void CloseOculusDevice(void) +{ + UnloadOculusMirror(session, mirror); // Unload Oculus mirror buffer + UnloadOculusBuffer(session, buffer); // Unload Oculus texture buffers + + ovr_Destroy(session); // Free Oculus session data + ovr_Shutdown(); // Close Oculus device connection +} + +// Update Oculus head position-orientation tracking +OCULUSAPI void UpdateOculusTracking(void) +{ + frameIndex++; + + ovrPosef eyePoses[2]; + ovr_GetEyePoses(session, frameIndex, ovrTrue, layer.viewScaleDesc.HmdToEyeOffset, eyePoses, &layer.eyeLayer.SensorSampleTime); + + layer.eyeLayer.RenderPose[0] = eyePoses[0]; + layer.eyeLayer.RenderPose[1] = eyePoses[1]; + + // Get session status information + ovrSessionStatus sessionStatus; + ovr_GetSessionStatus(session, &sessionStatus); + + if (sessionStatus.ShouldQuit) TraceLog(WARNING, "OVR: Session should quit..."); + if (sessionStatus.ShouldRecenter) ovr_RecenterTrackingOrigin(session); + //if (sessionStatus.HmdPresent) // HMD is present. + //if (sessionStatus.DisplayLost) // HMD was unplugged or the display driver was manually disabled or encountered a TDR. + //if (sessionStatus.HmdMounted) // HMD is on the user's head. + //if (sessionStatus.IsVisible) // the game or experience has VR focus and is visible in the HMD. +} + +// Setup Oculus buffers for drawing +OCULUSAPI void BeginOculusDrawing(void) +{ + GLuint currentTexId; + int currentIndex; + + ovr_GetTextureSwapChainCurrentIndex(session, buffer.textureChain, ¤tIndex); + ovr_GetTextureSwapChainBufferGL(session, buffer.textureChain, currentIndex, ¤tTexId); + + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, buffer.fboId); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, currentTexId, 0); + //glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, buffer.depthId, 0); // Already binded +} + +// Finish Oculus drawing and blit framebuffer to mirror +OCULUSAPI void EndOculusDrawing(void) +{ + // Unbind current framebuffer (Oculus buffer) + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + + ovr_CommitTextureSwapChain(session, buffer.textureChain); + + ovrLayerHeader *layers = &layer.eyeLayer.Header; + ovr_SubmitFrame(session, frameIndex, &layer.viewScaleDesc, &layers, 1); + + // Blit mirror texture to back buffer + BlitOculusMirror(session, mirror); +} + // Load Oculus required buffers: texture-swap-chain, fbo, texture-depth static OculusBuffer LoadOculusBuffer(ovrSession session, int width, int height) { @@ -218,9 +218,9 @@ typedef enum { OPENGL_11 = 1, OPENGL_21, OPENGL_33, OPENGL_ES_20 } GlVersion; // Light type typedef struct LightData { unsigned int id; // Light unique id - int type; // Light type: LIGHT_POINT, LIGHT_DIRECTIONAL, LIGHT_SPOT bool enabled; // Light enabled - + int type; // Light type: LIGHT_POINT, LIGHT_DIRECTIONAL, LIGHT_SPOT + Vector3 position; // Light position Vector3 target; // Light target: LIGHT_DIRECTIONAL and LIGHT_SPOT (cone direction target) float radius; // Light attenuation radius light intensity reduced with distance (world distance) @@ -239,6 +239,19 @@ typedef enum { OPENGL_11 = 1, OPENGL_21, OPENGL_33, OPENGL_ES_20 } GlVersion; // TraceLog message types typedef enum { INFO = 0, ERROR, WARNING, DEBUG, OTHER } TraceLogType; + + // Head Mounted Display devices + typedef enum { + HMD_DEFAULT_DEVICE = 0, + HMD_OCULUS_RIFT_DK2, + HMD_OCULUS_RIFT_CV1, + HMD_VALVE_HTC_VIVE, + HMD_SAMSUNG_GEAR_VR, + HMD_GOOGLE_CARDBOARD, + HMD_SONY_PLAYSTATION_VR, + HMD_RAZER_OSVR, + HMD_FOVE_VR, + } HmdDevice; #endif #ifdef __cplusplus @@ -350,6 +363,7 @@ Light CreateLight(int type, Vector3 position, Color diffuse); // Create a void DestroyLight(Light light); // Destroy a light and take it out of the list void TraceLog(int msgType, const char *text, ...); +float *MatrixToFloat(Matrix mat); void InitVrDevice(int hmdDevice); // Init VR device void CloseVrDevice(void); // Close VR device @@ -358,6 +372,13 @@ void BeginVrDrawing(void); // Begin VR drawing configuration void EndVrDrawing(void); // End VR drawing process (and desktop mirror) bool IsVrDeviceReady(void); // Detect if VR device (or simulator) is ready void ToggleVrMode(void); // Enable/Disable VR experience (device or simulator) + +// Oculus Rift API for direct access the device (no simulator) +bool InitOculusDevice(void); // Initialize Oculus device (returns true if success) +void CloseOculusDevice(void); // Close Oculus device +void UpdateOculusTracking(void); // Update Oculus head position-orientation tracking +void BeginOculusDrawing(void); // Setup Oculus buffers for drawing +void EndOculusDrawing(void); // Finish Oculus drawing and blit framebuffer to mirror #endif #ifdef __cplusplus diff --git a/src/shader_distortion.h b/src/shader_distortion.h new file mode 100644 index 00000000..75653e12 --- /dev/null +++ b/src/shader_distortion.h @@ -0,0 +1,106 @@ + +// Vertex shader definition to embed, no external file required +static const char vDistortionShaderStr[] = +#if defined(GRAPHICS_API_OPENGL_21) +"#version 120 \n" +#elif defined(GRAPHICS_API_OPENGL_ES2) +"#version 100 \n" +#endif +#if defined(GRAPHICS_API_OPENGL_ES2) || defined(GRAPHICS_API_OPENGL_21) +"attribute vec3 vertexPosition; \n" +"attribute vec2 vertexTexCoord; \n" +"attribute vec4 vertexColor; \n" +"varying vec2 fragTexCoord; \n" +"varying vec4 fragColor; \n" +#elif defined(GRAPHICS_API_OPENGL_33) +"#version 330 \n" +"in vec3 vertexPosition; \n" +"in vec2 vertexTexCoord; \n" +"in vec4 vertexColor; \n" +"out vec2 fragTexCoord; \n" +"out vec4 fragColor; \n" +#endif +"uniform mat4 mvpMatrix; \n" +"void main() \n" +"{ \n" +" fragTexCoord = vertexTexCoord; \n" +" fragColor = vertexColor; \n" +" gl_Position = mvpMatrix*vec4(vertexPosition, 1.0); \n" +"} \n"; + +// Fragment shader definition to embed, no external file required +static const char fDistortionShaderStr[] = +#if defined(GRAPHICS_API_OPENGL_21) +"#version 120 \n" +#elif defined(GRAPHICS_API_OPENGL_ES2) +"#version 100 \n" +"precision mediump float; \n" // precision required for OpenGL ES2 (WebGL) +#endif +#if defined(GRAPHICS_API_OPENGL_ES2) || defined(GRAPHICS_API_OPENGL_21) +"varying vec2 fragTexCoord; \n" +"varying vec4 fragColor; \n" +#elif defined(GRAPHICS_API_OPENGL_33) +"#version 330 \n" +"in vec2 fragTexCoord; \n" +"in vec4 fragColor; \n" +"out vec4 finalColor; \n" +#endif +"uniform sampler2D texture0; \n" +#if defined(GRAPHICS_API_OPENGL_ES2) || defined(GRAPHICS_API_OPENGL_21) +"uniform vec2 leftLensCenter; \n" +"uniform vec2 rightLensCenter; \n" +"uniform vec2 leftScreenCenter; \n" +"uniform vec2 rightScreenCenter; \n" +"uniform vec2 scale; \n" +"uniform vec2 scaleIn; \n" +"uniform vec4 hmdWarpParam; \n" +"uniform vec4 chromaAbParam; \n" +#elif defined(GRAPHICS_API_OPENGL_33) +"uniform vec2 leftLensCenter = vec2(0.288, 0.5); \n" +"uniform vec2 rightLensCenter = vec2(0.712, 0.5); \n" +"uniform vec2 leftScreenCenter = vec2(0.25, 0.5); \n" +"uniform vec2 rightScreenCenter = vec2(0.75, 0.5); \n" +"uniform vec2 scale = vec2(0.25, 0.45); \n" +"uniform vec2 scaleIn = vec2(4, 2.2222); \n" +"uniform vec4 hmdWarpParam = vec4(1, 0.22, 0.24, 0); \n" +"uniform vec4 chromaAbParam = vec4(0.996, -0.004, 1.014, 0.0); \n" +#endif +"void main() \n" +"{ \n" +" vec2 lensCenter = fragTexCoord.x < 0.5 ? leftLensCenter : rightLensCenter; \n" +" vec2 screenCenter = fragTexCoord.x < 0.5 ? leftScreenCenter : rightScreenCenter; \n" +" vec2 theta = (fragTexCoord - lensCenter)*scaleIn; \n" +" float rSq = theta.x*theta.x + theta.y*theta.y; \n" +" vec2 theta1 = theta*(hmdWarpParam.x + hmdWarpParam.y*rSq + hmdWarpParam.z*rSq*rSq + hmdWarpParam.w*rSq*rSq*rSq); \n" +" vec2 thetaBlue = theta1*(chromaAbParam.z + chromaAbParam.w*rSq); \n" +" vec2 tcBlue = lensCenter + scale*thetaBlue; \n" +" if (any(bvec2(clamp(tcBlue, screenCenter - vec2(0.25, 0.5), screenCenter + vec2(0.25, 0.5)) - tcBlue))) \n" +" { \n" +#if defined(GRAPHICS_API_OPENGL_ES2) || defined(GRAPHICS_API_OPENGL_21) +" gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); \n" +#elif defined(GRAPHICS_API_OPENGL_33) +" finalColor = vec4(0.0, 0.0, 0.0, 1.0); \n" +#endif +" } \n" +" else \n" +" { \n" +#if defined(GRAPHICS_API_OPENGL_ES2) || defined(GRAPHICS_API_OPENGL_21) +" float blue = texture2D(texture0, tcBlue).b; \n" +" vec2 tcGreen = lensCenter + scale*theta1; \n" +" float green = texture2D(texture0, tcGreen).g; \n" +#elif defined(GRAPHICS_API_OPENGL_33) +" float blue = texture(texture0, tcBlue).b; \n" +" vec2 tcGreen = lensCenter + scale*theta1; \n" +" float green = texture(texture0, tcGreen).g; \n" +#endif +" vec2 thetaRed = theta1*(chromaAbParam.x + chromaAbParam.y*rSq); \n" +" vec2 tcRed = lensCenter + scale*thetaRed; \n" +#if defined(GRAPHICS_API_OPENGL_ES2) || defined(GRAPHICS_API_OPENGL_21) +" float red = texture2D(texture0, tcRed).r; \n" +" gl_FragColor = vec4(red, green, blue, 1.0); \n" +#elif defined(GRAPHICS_API_OPENGL_33) +" float red = texture(texture0, tcRed).r; \n" +" finalColor = vec4(red, green, blue, 1.0); \n" +#endif +" } \n" +"} \n"; diff --git a/src/standard_shader.h b/src/shader_standard.h index e1668ae7..deae7fe1 100644 --- a/src/standard_shader.h +++ b/src/shader_standard.h @@ -78,7 +78,6 @@ static const char fStandardShaderStr[] = " float radius; \n" " float coneAngle; }; \n" "const int maxLights = 8; \n" -"uniform int lightsCount; \n" "uniform Light lights[maxLights]; \n" "\n" "vec3 CalcPointLight(Light l, vec3 n, vec3 v, float s) \n" @@ -157,7 +156,7 @@ static const char fStandardShaderStr[] = #elif defined(GRAPHICS_API_OPENGL_33) " if (useSpecular == 1) spec *= normalize(texture(texture2, fragTexCoord).r);\n" #endif -" for (int i = 0; i < lightsCount; i++)\n" +" for (int i = 0; i < maxLights; i++)\n" " {\n" " if (lights[i].enabled == 1)\n" " {\n" diff --git a/src/shapes.c b/src/shapes.c index 2a4e19c2..d9b172f1 100644 --- a/src/shapes.c +++ b/src/shapes.c @@ -276,12 +276,29 @@ void DrawRectangleLines(int posX, int posY, int width, int height, Color color) // Draw a triangle void DrawTriangle(Vector2 v1, Vector2 v2, Vector2 v3, Color color) { - rlBegin(RL_TRIANGLES); - rlColor4ub(color.r, color.g, color.b, color.a); - rlVertex2f(v1.x, v1.y); - rlVertex2f(v2.x, v2.y); - rlVertex2f(v3.x, v3.y); - rlEnd(); + if (rlGetVersion() == OPENGL_11) + { + rlBegin(RL_TRIANGLES); + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(v1.x, v1.y); + rlVertex2f(v2.x, v2.y); + rlVertex2f(v3.x, v3.y); + rlEnd(); + } + else if ((rlGetVersion() == OPENGL_21) || (rlGetVersion() == OPENGL_33) || (rlGetVersion() == OPENGL_ES_20)) + { + rlEnableTexture(GetDefaultTexture().id); // Default white texture + + rlBegin(RL_QUADS); + rlColor4ub(color.r, color.g, color.b, color.a); + rlVertex2f(v1.x, v1.y); + rlVertex2f(v2.x, v2.y); + rlVertex2f(v2.x, v2.y); + rlVertex2f(v3.x, v3.y); + rlEnd(); + + rlDisableTexture(); + } } void DrawTriangleLines(Vector2 v1, Vector2 v2, Vector2 v3, Color color) @@ -782,12 +782,15 @@ static SpriteFont LoadBMFont(const char *fileName) char *texPath = NULL; char *lastSlash = NULL; - lastSlash = strrchr(fileName, '/'); // you need escape character - texPath = malloc(strlen(fileName) - strlen(lastSlash) + 1 + strlen(texFileName) + 1); - memcpy(texPath, fileName, strlen(fileName) - strlen(lastSlash)); - strcat(texPath, "/"); - strcat(texPath, texFileName); - strcat(texPath, "\0"); + lastSlash = strrchr(fileName, '/'); + + // NOTE: We need some extra space to avoid memory corruption on next allocations! + texPath = malloc(strlen(fileName) - strlen(lastSlash) + strlen(texFileName) + 4); + + // NOTE: strcat() and strncat() required a '\0' terminated string to work! + *texPath = '\0'; + strncat(texPath, fileName, strlen(fileName) - strlen(lastSlash) + 1); + strncat(texPath, texFileName, strlen(texFileName)); TraceLog(DEBUG, "[%s] Font texture loading path: %s", fileName, texPath); @@ -828,7 +831,7 @@ static SpriteFont LoadBMFont(const char *fileName) else if (unorderedChars) TraceLog(WARNING, "BMFont not supported: unordered chars data, falling back to default font"); // NOTE: Font data could be not ordered by charId: 32,33,34,35... raylib does not support unordered BMFonts - if ((firstChar != FONT_FIRST_CHAR) || (unorderedChars)) + if ((firstChar != FONT_FIRST_CHAR) || (unorderedChars) || (font.texture.id == 0)) { UnloadSpriteFont(font); font = GetDefaultFont(); |
