Audio: Replace SFML solution by using OpenAL directly
This is a major change in the Audio module that comes with many changes throughout the codebase and dependency list. The main gist is that we're finally nuking the last pieces of SFML from the project. sfml-audio brought with itself unneeded and big dependencies (libsndfile, libvorbisenc) while at the same time limiting the amount of audio formats mkxp can support (eg. we now get mp3 for free, and wma/midi can be implemented by extending SDL_sound directly). The increased control gained by interfacing with OpenAL directly will also allow for easy integration of a dedicated audio stretcher (librubberband), as well as enable us to implement looped ogg vorbis (via the 'LOOPSTART'/'LOOPLENGTH' tags), as required by RGSS2, in the future. The FileSystem class has had its SFML parts removed. Aditionally, audio file extensions to be supplemented are now automatically detected based on how SDL_sound was built (ie. if no mp3 support was built, mkxp won't try to look for *.mp3 files). The final used extension can be optionally returned by 'openRead' calls so SDL_sound and SDL2_image can immediately choose the right decoder. The OpenAL context is created and destroyed in main.cpp along side the GL context.
This commit is contained in:
parent
47db7165a6
commit
64f35071ab
11
mkxp.pro
11
mkxp.pro
|
@ -1,7 +1,7 @@
|
|||
|
||||
|
||||
TEMPLATE = app
|
||||
QT = core
|
||||
QT =
|
||||
TARGET = mkxp
|
||||
DEPENDPATH += src shader assets
|
||||
INCLUDEPATH += . src
|
||||
|
@ -14,9 +14,9 @@ isEmpty(BINDING) {
|
|||
CONFIG += $$BINDING
|
||||
|
||||
unix {
|
||||
CONFIG += link_pkgconfig
|
||||
PKGCONFIG += sigc++-2.0 glew pixman-1 zlib sdl2 SDL2_image SDL2_ttf sfml-audio
|
||||
LIBS += -lphysfs
|
||||
CONFIG += link_pkgconfig
|
||||
PKGCONFIG += QtCore sigc++-2.0 glew pixman-1 zlib \
|
||||
physfs sdl2 SDL2_image SDL2_ttf SDL_sound openal
|
||||
}
|
||||
|
||||
# 'slots' keyword fucks with libsigc++
|
||||
|
@ -62,7 +62,8 @@ HEADERS += \
|
|||
src/config.h \
|
||||
src/tileatlas.h \
|
||||
src/perftimer.h \
|
||||
src/sharedstate.h
|
||||
src/sharedstate.h \
|
||||
src/al-util.h
|
||||
|
||||
SOURCES += \
|
||||
src/main.cpp \
|
||||
|
|
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
** al-util.h
|
||||
**
|
||||
** This file is part of mkxp.
|
||||
**
|
||||
** Copyright (C) 2013 Jonas Kulla <Nyocurio@gmail.com>
|
||||
**
|
||||
** mkxp is free software: you can redistribute it and/or modify
|
||||
** it under the terms of the GNU General Public License as published by
|
||||
** the Free Software Foundation, either version 2 of the License, or
|
||||
** (at your option) any later version.
|
||||
**
|
||||
** mkxp is distributed in the hope that it will be useful,
|
||||
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
** GNU General Public License for more details.
|
||||
**
|
||||
** You should have received a copy of the GNU General Public License
|
||||
** along with mkxp. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ALUTIL_H
|
||||
#define ALUTIL_H
|
||||
|
||||
#include <al.h>
|
||||
#include <alext.h>
|
||||
|
||||
namespace AL
|
||||
{
|
||||
|
||||
#define DEF_AL_ID \
|
||||
struct ID \
|
||||
{ \
|
||||
ALuint al; \
|
||||
explicit ID(ALuint al = 0) \
|
||||
: al(al) \
|
||||
{} \
|
||||
ID &operator=(const ID &o) \
|
||||
{ \
|
||||
al = o.al; \
|
||||
return *this; \
|
||||
} \
|
||||
bool operator==(const ID &o) const \
|
||||
{ \
|
||||
return al == o.al; \
|
||||
} \
|
||||
};
|
||||
|
||||
namespace Buffer
|
||||
{
|
||||
DEF_AL_ID
|
||||
|
||||
inline Buffer::ID gen()
|
||||
{
|
||||
Buffer::ID id;
|
||||
alGenBuffers(1, &id.al);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
inline void del(Buffer::ID id)
|
||||
{
|
||||
alDeleteBuffers(1, &id.al);
|
||||
}
|
||||
|
||||
inline void uploadData(Buffer::ID id, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq)
|
||||
{
|
||||
alBufferData(id.al, format, data, size, freq);
|
||||
}
|
||||
|
||||
inline ALint getInteger(Buffer::ID id, ALenum prop)
|
||||
{
|
||||
ALint value;
|
||||
alGetBufferi(id.al, prop, &value);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
inline ALint getSize(Buffer::ID id)
|
||||
{
|
||||
return getInteger(id, AL_SIZE);
|
||||
}
|
||||
|
||||
inline ALint getBits(Buffer::ID id)
|
||||
{
|
||||
return getInteger(id, AL_BITS);
|
||||
}
|
||||
}
|
||||
|
||||
namespace Source
|
||||
{
|
||||
DEF_AL_ID
|
||||
|
||||
inline Source::ID gen()
|
||||
{
|
||||
Source::ID id;
|
||||
alGenSources(1, &id.al);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
inline void del(Source::ID id)
|
||||
{
|
||||
alDeleteSources(1, &id.al);
|
||||
}
|
||||
|
||||
inline void attachBuffer(Source::ID id, Buffer::ID buffer)
|
||||
{
|
||||
alSourcei(id.al, AL_BUFFER, buffer.al);
|
||||
}
|
||||
|
||||
inline void detachBuffer(Source::ID id)
|
||||
{
|
||||
attachBuffer(id, Buffer::ID(0));
|
||||
}
|
||||
|
||||
inline void queueBuffer(Source::ID id, Buffer::ID buffer)
|
||||
{
|
||||
alSourceQueueBuffers(id.al, 1, &buffer.al);
|
||||
}
|
||||
|
||||
inline Buffer::ID unqueueBuffer(Source::ID id)
|
||||
{
|
||||
Buffer::ID buffer;
|
||||
alSourceUnqueueBuffers(id.al, 1, &buffer.al);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
inline ALint getInteger(Source::ID id, ALenum prop)
|
||||
{
|
||||
ALint value;
|
||||
alGetSourcei(id.al, prop, &value);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
inline ALint getProcBufferCount(Source::ID id)
|
||||
{
|
||||
return getInteger(id, AL_BUFFERS_PROCESSED);
|
||||
}
|
||||
|
||||
inline ALenum getState(Source::ID id)
|
||||
{
|
||||
return getInteger(id, AL_SOURCE_STATE);
|
||||
}
|
||||
|
||||
inline ALint getSampleOffset(Source::ID id)
|
||||
{
|
||||
return getInteger(id, AL_SAMPLE_OFFSET);
|
||||
}
|
||||
|
||||
inline void setVolume(Source::ID id, float value)
|
||||
{
|
||||
alSourcef(id.al, AL_GAIN, value);
|
||||
}
|
||||
|
||||
inline void setPitch(Source::ID id, float value)
|
||||
{
|
||||
alSourcef(id.al, AL_PITCH, value);
|
||||
}
|
||||
|
||||
inline void play(Source::ID id)
|
||||
{
|
||||
alSourcePlay(id.al);
|
||||
}
|
||||
|
||||
inline void stop(Source::ID id)
|
||||
{
|
||||
alSourceStop(id.al);
|
||||
}
|
||||
|
||||
inline void pause(Source::ID id)
|
||||
{
|
||||
alSourcePause(id.al);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // ALUTIL_H
|
1815
src/audio.cpp
1815
src/audio.cpp
File diff suppressed because it is too large
Load Diff
|
@ -26,6 +26,8 @@
|
|||
|
||||
#include "physfs.h"
|
||||
|
||||
#include <SDL_sound.h>
|
||||
|
||||
#include <QHash>
|
||||
#include <QSet>
|
||||
#include <QByteArray>
|
||||
|
@ -536,112 +538,35 @@ static const PHYSFS_Archiver RGSS_Archiver =
|
|||
RGSS_closeArchive
|
||||
};
|
||||
|
||||
FileStream::FileStream(PHYSFS_File *file)
|
||||
{
|
||||
p = file;
|
||||
}
|
||||
|
||||
FileStream::~FileStream()
|
||||
{
|
||||
// if (p)
|
||||
// PHYSFS_close(p);
|
||||
}
|
||||
|
||||
void FileStream::operator=(const FileStream &o)
|
||||
{
|
||||
p = o.p;
|
||||
}
|
||||
|
||||
sf::Int64 FileStream::read(void *data, sf::Int64 size)
|
||||
{
|
||||
if (!p)
|
||||
return -1;
|
||||
|
||||
return PHYSFS_readBytes(p, data, size);
|
||||
}
|
||||
|
||||
sf::Int64 FileStream::seek(sf::Int64 position)
|
||||
{
|
||||
if (!p)
|
||||
return -1;
|
||||
|
||||
int success = PHYSFS_seek(p, (PHYSFS_uint64) position);
|
||||
|
||||
return success ? position : -1;
|
||||
}
|
||||
|
||||
sf::Int64 FileStream::tell()
|
||||
{
|
||||
if (!p)
|
||||
return -1;
|
||||
|
||||
return PHYSFS_tell(p);
|
||||
}
|
||||
|
||||
sf::Int64 FileStream::getSize()
|
||||
{
|
||||
if (!p)
|
||||
return -1;
|
||||
|
||||
return PHYSFS_fileLength(p);
|
||||
}
|
||||
|
||||
sf::Int64 FileStream::write(const void *data, sf::Int64 size)
|
||||
{
|
||||
if (!p)
|
||||
return -1;
|
||||
|
||||
return PHYSFS_writeBytes(p, data, size);
|
||||
}
|
||||
|
||||
void FileStream::close()
|
||||
{
|
||||
if (p)
|
||||
{
|
||||
PHYSFS_close(p);
|
||||
p = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static const char *imgExt[] =
|
||||
{
|
||||
"jpg",
|
||||
"png"
|
||||
};
|
||||
|
||||
static const char *audExt[] =
|
||||
{
|
||||
// "mid",
|
||||
// "midi",
|
||||
// "mp3",
|
||||
// "wma",
|
||||
"ogg",
|
||||
"wav"
|
||||
};
|
||||
|
||||
static const char *fonExt[] =
|
||||
{
|
||||
"ttf"
|
||||
};
|
||||
|
||||
struct FileExtensions
|
||||
{
|
||||
const char **ext;
|
||||
int count;
|
||||
} fileExtensions[] =
|
||||
{
|
||||
{ imgExt, ARRAY_SIZE(imgExt) },
|
||||
{ audExt, ARRAY_SIZE(audExt) },
|
||||
{ fonExt, ARRAY_SIZE(fonExt) }
|
||||
};
|
||||
|
||||
struct FileSystemPrivate
|
||||
{
|
||||
/* All keys are lower case */
|
||||
QHash<QByteArray, QByteArray> pathCache;
|
||||
|
||||
QList<QByteArray> extensions[FileSystem::Undefined];
|
||||
|
||||
/* Attempt to locate an extension string in a filename.
|
||||
* Either a pointer into the input string pointing at the
|
||||
* extension, or null is returned */
|
||||
const char *findExt(const char *filename)
|
||||
{
|
||||
int len;
|
||||
|
||||
for (len = strlen(filename); len > 0; --len)
|
||||
{
|
||||
if (filename[len] == '/')
|
||||
return 0;
|
||||
|
||||
if (filename[len] == '.')
|
||||
return &filename[len+1];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *completeFileName(const char *filename,
|
||||
FileSystem::FileType type)
|
||||
FileSystem::FileType type,
|
||||
const char **foundExt)
|
||||
{
|
||||
char buff[512];
|
||||
size_t i;
|
||||
|
@ -654,30 +579,48 @@ struct FileSystemPrivate
|
|||
QByteArray key(buff);
|
||||
|
||||
if (pathCache.contains(key))
|
||||
{
|
||||
/* The extension might already be included here,
|
||||
* so try to find it */
|
||||
if (foundExt)
|
||||
*foundExt = findExt(filename);
|
||||
|
||||
return pathCache[key].constData();
|
||||
}
|
||||
|
||||
char buff2[512];
|
||||
|
||||
if (type != FileSystem::Undefined)
|
||||
{
|
||||
FileExtensions *extTest = &fileExtensions[type];
|
||||
for (int i = 0; i < extTest->count; ++i)
|
||||
QList<QByteArray> &extList = extensions[type];
|
||||
for (int i = 0; i < extList.count(); ++i)
|
||||
{
|
||||
snprintf(buff2, sizeof(buff2), "%s.%s", buff, extTest->ext[i]);
|
||||
const char *ext = extList[i].constData();
|
||||
|
||||
snprintf(buff2, sizeof(buff2), "%s.%s", buff, ext);
|
||||
key = buff2;
|
||||
|
||||
if (pathCache.contains(key))
|
||||
{
|
||||
if (foundExt)
|
||||
*foundExt = ext;
|
||||
|
||||
return pathCache[key].constData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (foundExt)
|
||||
*foundExt = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
PHYSFS_File *openReadInt(const char *filename,
|
||||
FileSystem::FileType type)
|
||||
FileSystem::FileType type,
|
||||
const char **foundExt)
|
||||
{
|
||||
const char *foundName = completeFileName(filename, type);
|
||||
const char *foundName = completeFileName(filename, type, foundExt);
|
||||
|
||||
if (!foundName)
|
||||
throw Exception(Exception::NoFileError,
|
||||
|
@ -696,6 +639,34 @@ FileSystem::FileSystem(const char *argv0,
|
|||
{
|
||||
p = new FileSystemPrivate;
|
||||
|
||||
/* Image extensions */
|
||||
p->extensions[Image] << "jpg" << "png";
|
||||
|
||||
/* Audio extensions */
|
||||
const Sound_DecoderInfo **di;
|
||||
for (di = Sound_AvailableDecoders(); *di; ++di)
|
||||
{
|
||||
const char **ext;
|
||||
for (ext = (*di)->extensions; *ext; ++ext)
|
||||
{
|
||||
/* All reported extension are uppercase,
|
||||
* so we need to hammer them down first */
|
||||
char buf[16];
|
||||
for (size_t i = 0; i < sizeof(buf); ++i)
|
||||
{
|
||||
buf[i] = tolower((*ext)[i]);
|
||||
|
||||
if (!buf[i])
|
||||
break;
|
||||
}
|
||||
|
||||
p->extensions[Audio] << buf;
|
||||
}
|
||||
}
|
||||
|
||||
/* Font extensions */
|
||||
p->extensions[Font] << "ttf";
|
||||
|
||||
PHYSFS_init(argv0);
|
||||
PHYSFS_registerArchiver(&RGSS_Archiver);
|
||||
|
||||
|
@ -745,14 +716,6 @@ void FileSystem::createPathCache()
|
|||
PHYSFS_enumerateFilesCallback(".", cacheEnumCB, p);
|
||||
}
|
||||
|
||||
FileStream FileSystem::openRead(const char *filename,
|
||||
FileType type)
|
||||
{
|
||||
PHYSFS_File *handle = p->openReadInt(filename, type);
|
||||
|
||||
return FileStream(handle);
|
||||
}
|
||||
|
||||
static inline PHYSFS_File *sdlPHYS(SDL_RWops *ops)
|
||||
{
|
||||
return static_cast<PHYSFS_File*>(ops->hidden.unknown.data1);
|
||||
|
@ -847,9 +810,10 @@ const Uint32 SDL_RWOPS_PHYSFS = SDL_RWOPS_UNKNOWN+10;
|
|||
void FileSystem::openRead(SDL_RWops &ops,
|
||||
const char *filename,
|
||||
FileType type,
|
||||
bool freeOnClose)
|
||||
bool freeOnClose,
|
||||
const char **foundExt)
|
||||
{
|
||||
PHYSFS_File *handle = p->openReadInt(filename, type);
|
||||
PHYSFS_File *handle = p->openReadInt(filename, type, foundExt);
|
||||
|
||||
ops.size = SDL_RWopsSize;
|
||||
ops.seek = SDL_RWopsSeek;
|
||||
|
@ -867,7 +831,7 @@ void FileSystem::openRead(SDL_RWops &ops,
|
|||
|
||||
bool FileSystem::exists(const char *filename, FileType type)
|
||||
{
|
||||
const char *foundName = p->completeFileName(filename, type);
|
||||
const char *foundName = p->completeFileName(filename, type, 0);
|
||||
|
||||
return (foundName != 0);
|
||||
}
|
||||
|
|
|
@ -22,33 +22,8 @@
|
|||
#ifndef FILESYSTEM_H
|
||||
#define FILESYSTEM_H
|
||||
|
||||
#include "SFML/System/InputStream.hpp"
|
||||
|
||||
#include "SDL_rwops.h"
|
||||
|
||||
struct PHYSFS_File;
|
||||
|
||||
class FileStream : public sf::InputStream
|
||||
{
|
||||
public:
|
||||
FileStream(PHYSFS_File *);
|
||||
~FileStream();
|
||||
|
||||
void operator=(const FileStream &o);
|
||||
|
||||
sf::Int64 read(void *data, sf::Int64 size);
|
||||
sf::Int64 seek(sf::Int64 position);
|
||||
sf::Int64 tell();
|
||||
sf::Int64 getSize();
|
||||
|
||||
sf::Int64 write(const void *data, sf::Int64 size);
|
||||
|
||||
void close();
|
||||
|
||||
private:
|
||||
PHYSFS_File *p; /* NULL denotes invalid stream */
|
||||
};
|
||||
|
||||
struct FileSystemPrivate;
|
||||
|
||||
class FileSystem
|
||||
|
@ -72,13 +47,11 @@ public:
|
|||
Undefined
|
||||
};
|
||||
|
||||
FileStream openRead(const char *filename,
|
||||
FileType type = Undefined);
|
||||
|
||||
void openRead(SDL_RWops &ops,
|
||||
const char *filename,
|
||||
FileType type = Undefined,
|
||||
bool freeOnClose = false);
|
||||
bool freeOnClose = false,
|
||||
const char **foundExt = 0);
|
||||
|
||||
bool exists(const char *filename,
|
||||
FileType type = Undefined);
|
||||
|
|
55
src/main.cpp
55
src/main.cpp
|
@ -20,10 +20,12 @@
|
|||
*/
|
||||
|
||||
#include "glew.h"
|
||||
#include <alc.h>
|
||||
|
||||
#include "SDL.h"
|
||||
#include "SDL_image.h"
|
||||
#include "SDL_ttf.h"
|
||||
#include <SDL_sound.h>
|
||||
|
||||
#include "sharedstate.h"
|
||||
#include "eventthread.h"
|
||||
|
@ -77,7 +79,7 @@ int rgssThreadFun(void *userdata)
|
|||
{
|
||||
RGSSThreadData *threadData = static_cast<RGSSThreadData*>(userdata);
|
||||
SDL_Window *win = threadData->window;
|
||||
SDL_GLContext ctx;
|
||||
SDL_GLContext glCtx;
|
||||
|
||||
/* Setup GL context */
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||
|
@ -85,9 +87,9 @@ int rgssThreadFun(void *userdata)
|
|||
if (threadData->config.debugMode)
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG);
|
||||
|
||||
ctx = SDL_GL_CreateContext(win);
|
||||
glCtx = SDL_GL_CreateContext(win);
|
||||
|
||||
if (!ctx)
|
||||
if (!glCtx)
|
||||
{
|
||||
rgssThreadError(threadData, QByteArray("Error creating context: ") + SDL_GetError());
|
||||
return 0;
|
||||
|
@ -96,7 +98,7 @@ int rgssThreadFun(void *userdata)
|
|||
if (glewInit() != GLEW_OK)
|
||||
{
|
||||
rgssThreadError(threadData, "Error initializing glew");
|
||||
SDL_GL_DeleteContext(ctx);
|
||||
SDL_GL_DeleteContext(glCtx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -110,7 +112,7 @@ int rgssThreadFun(void *userdata)
|
|||
if (!GLEW_VERSION_2_0)
|
||||
{
|
||||
rgssThreadError(threadData, "At least OpenGL 2.0 is required");
|
||||
SDL_GL_DeleteContext(ctx);
|
||||
SDL_GL_DeleteContext(glCtx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -121,7 +123,7 @@ int rgssThreadFun(void *userdata)
|
|||
{
|
||||
rgssThreadError(threadData, QByteArray("Required GL extension \"")
|
||||
+ reqExt[i] + "\" not present");
|
||||
SDL_GL_DeleteContext(ctx);
|
||||
SDL_GL_DeleteContext(glCtx);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
@ -130,6 +132,28 @@ int rgssThreadFun(void *userdata)
|
|||
|
||||
DebugLogger dLogger;
|
||||
|
||||
/* Setup AL context */
|
||||
ALCdevice *alcDev = alcOpenDevice(0);
|
||||
|
||||
if (!alcDev)
|
||||
{
|
||||
rgssThreadError(threadData, "Error opening OpenAL device");
|
||||
SDL_GL_DeleteContext(glCtx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ALCcontext *alcCtx = alcCreateContext(alcDev, 0);
|
||||
|
||||
if (!alcCtx)
|
||||
{
|
||||
rgssThreadError(threadData, "Error creating OpenAL context");
|
||||
alcCloseDevice(alcDev);
|
||||
SDL_GL_DeleteContext(glCtx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
alcMakeContextCurrent(alcCtx);
|
||||
|
||||
SharedState::initInstance(threadData);
|
||||
|
||||
/* Start script execution */
|
||||
|
@ -140,7 +164,10 @@ int rgssThreadFun(void *userdata)
|
|||
|
||||
SharedState::finiInstance();
|
||||
|
||||
SDL_GL_DeleteContext(ctx);
|
||||
alcDestroyContext(alcCtx);
|
||||
alcCloseDevice(alcDev);
|
||||
|
||||
SDL_GL_DeleteContext(glCtx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -155,6 +182,7 @@ int main(int, char *argv[])
|
|||
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0)
|
||||
{
|
||||
qDebug() << "Error initializing SDL:" << SDL_GetError();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -163,6 +191,7 @@ int main(int, char *argv[])
|
|||
{
|
||||
qDebug() << "Error initializing SDL_image:" << SDL_GetError();
|
||||
SDL_Quit();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -171,6 +200,17 @@ int main(int, char *argv[])
|
|||
qDebug() << "Error initializing SDL_ttf:" << SDL_GetError();
|
||||
IMG_Quit();
|
||||
SDL_Quit();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (Sound_Init() == 0)
|
||||
{
|
||||
qDebug() << "Error initializing SDL_sound:" << Sound_GetError();
|
||||
TTF_Quit();
|
||||
IMG_Quit();
|
||||
SDL_Quit();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -241,6 +281,7 @@ int main(int, char *argv[])
|
|||
|
||||
SDL_DestroyWindow(win);
|
||||
|
||||
Sound_Quit();
|
||||
TTF_Quit();
|
||||
IMG_Quit();
|
||||
SDL_Quit();
|
||||
|
|
Loading…
Reference in New Issue