- redesign audio library to use Chunk and Music classes which align more closely with SDL Mix_Chunk and Mix_Music

- remove default texture index from Model class
- add support for two textures to Pad class
- restore SDL audio mixer initialization parameters to SDL defaults
- add optional log level to SDL error logging function
This commit is contained in:
ohsqueezy 2023-10-09 23:50:18 -04:00
parent 5046b4bcf1
commit d575307b15
11 changed files with 353 additions and 213 deletions

View File

@ -1,102 +1,138 @@
#include "Audio.hpp" #include "Audio.hpp"
BGM::BGM(Node* parent) : Node(parent) {} using namespace sb::audio;
BGM::BGM() : BGM(nullptr) {} Chunk::Chunk(const fs::path& path)
BGM::BGM(Node* parent, const fs::path& path) : BGM(parent)
{ {
load_music(path); load(path);
} }
BGM::BGM(const fs::path& path) : BGM(nullptr, path) {} void Chunk::load(const fs::path& path)
void BGM::load_music(const fs::path& path)
{ {
music = Mix_LoadMUS(path.c_str()); chunk = std::shared_ptr<Mix_Chunk>(Mix_LoadWAV(path.c_str()), Mix_FreeChunk);
if (chunk.get() == nullptr)
{
std::ostringstream message;
message << "Unable to load audio chunk at " << path << ".";
Log::sdl_error(message.str(), Log::Level::WARN);
}
} }
void BGM::set_volume(float v) std::uint8_t Chunk::convert_volume(float volume)
{ {
volume = v; return std::clamp(static_cast<int>(std::round(volume * MIX_MAX_VOLUME)), 0, MIX_MAX_VOLUME);
} }
void BGM::play(int loops) float Chunk::volume() const
{ {
Mix_VolumeMusic(std::round(volume * 127)); std::uint8_t sdl_volume = Mix_VolumeChunk(chunk.get(), -1);
Mix_PlayMusic(music, loops); return static_cast<float>(sdl_volume) / static_cast<float>(MIX_MAX_VOLUME);
} }
BGM::~BGM() void Chunk::volume(float level)
{ {
Mix_FreeMusic(music); Mix_VolumeChunk(chunk.get(), convert_volume(level));
} }
SoundEffect::SoundEffect(Node* parent) : Node(parent) {} void Chunk::channel_volume(float volume)
SoundEffect::SoundEffect() : SoundEffect(nullptr) {}
SoundEffect::SoundEffect(Node* parent, const fs::path& path) : SoundEffect(parent)
{ {
load_chunk(path); int count = Mix_GroupCount(-1);
for (int channel = 0; channel < count; channel++)
{
if (Mix_GetChunk(channel) == chunk.get())
{
Mix_Volume(channel, convert_volume(volume));
}
}
} }
SoundEffect::SoundEffect(const fs::path& path) : SoundEffect(nullptr, path) {} void Chunk::loop()
void SoundEffect::load_chunk(const fs::path& path)
{
chunk = Mix_LoadWAV(path.c_str());
Mix_VolumeChunk(chunk, std::round(volume * 127));
}
void SoundEffect::play(float location)
{
play();
Box window = window_box();
location = std::clamp(location, window.left(), window.right());
float location_relative = (location - window.left()) / window.width();
int angle = 270 - location_relative * 180;
Mix_SetPosition(channel, angle, 0);
}
void SoundEffect::play()
{
channel = Mix_PlayChannel(-1, chunk, loops);
}
void SoundEffect::play_after(float delay)
{
play_animation.play_once(delay);
}
void SoundEffect::set_volume(float v)
{
volume = v;
Mix_VolumeChunk(chunk, std::round(volume * 127));
}
void SoundEffect::set_loop_count(int count)
{
loops = count;
}
void SoundEffect::set_loop_forever()
{ {
loops = -1; loops = -1;
} }
bool SoundEffect::playing(bool include_delay) const void Chunk::loop(int count)
{ {
if (include_delay) loops = count;
}
int Chunk::play(float fade, int channel)
{
/* Play the audio with a fade in time if any was specified. */
int assignment;
if (fade <= 0.0f)
{ {
if (play_animation.playing()) assignment = Mix_PlayChannel(channel, chunk.get(), loops);
}
else
{
int milliseconds = static_cast<int>(std::round(fade * 1000.0f));
assignment = Mix_FadeInChannel(channel, chunk.get(), loops, milliseconds);
}
/* Check if the audio is paused on other channels. If so, stop the audio on those channels. */
int count = Mix_GroupCount(-1);
for (int index = 0; index < count; index++)
{
if (index != assignment && Mix_GetChunk(index) == chunk.get() && Mix_Paused(index))
{ {
return true; Mix_HaltChannel(index);
} }
} }
for (int channel_ii = 0; channel_ii < Mix_GroupAvailable(-1); channel_ii++)
return assignment;
}
void Chunk::stop(float fade)
{
int count = Mix_GroupCount(-1);
for (int channel = 0; channel < count; channel++)
{ {
if (Mix_GetChunk(channel_ii) == chunk) if (Mix_GetChunk(channel) == chunk.get())
{
if (fade <= 0.0f)
{
Mix_HaltChannel(channel);
}
else
{
int milliseconds = static_cast<int>(std::round(fade * 1000.0f));
Mix_FadeOutChannel(channel, milliseconds);
}
}
}
}
void Chunk::pause()
{
int count = Mix_GroupCount(-1);
for (int channel = 0; channel < count; channel++)
{
if (Mix_GetChunk(channel) == chunk.get())
{
Mix_Pause(channel);
}
}
}
void Chunk::resume()
{
int count = Mix_GroupCount(-1);
for (int channel = 0; channel < count; channel++)
{
if (Mix_GetChunk(channel) == chunk.get())
{
Mix_Resume(channel);
}
}
}
bool Chunk::playing() const
{
int count = Mix_GroupCount(-1);
for (int channel = 0; channel < count; channel++)
{
if (Mix_GetChunk(channel) == chunk.get() && Mix_Playing(channel))
{ {
return true; return true;
} }
@ -104,32 +140,47 @@ bool SoundEffect::playing(bool include_delay) const
return false; return false;
} }
void SoundEffect::update(float timestamp) bool Chunk::paused() const
{ {
play_animation.update(timestamp); int count = Mix_GroupCount(-1);
} for (int channel = 0; channel < count; channel++)
SoundEffect::~SoundEffect()
{
Mix_FreeChunk(chunk);
}
Audio::Audio(Node* parent) : Node(parent) {}
void Audio::load_sfx()
{
load_directory(configuration()["audio"]["default-sfx-root"], sfx, this);
}
void Audio::load_bgm()
{
load_directory(configuration()["audio"]["default-bgm-root"], bgm, this);
}
void Audio::update(float timestamp)
{
for (auto& [name, sound_effect] : sfx)
{ {
sound_effect.update(timestamp); if (Mix_GetChunk(channel) == chunk.get() && Mix_Paused(channel))
{
return true;
}
}
return false;
}
bool Chunk::fading() const
{
int count = Mix_GroupCount(-1);
for (int channel = 0; channel < count; channel++)
{
if (Mix_GetChunk(channel) == chunk.get() && Mix_FadingChannel(channel))
{
return true;
}
}
return false;
}
Music::Music(const fs::path& path)
{
load(path);
}
void Music::load(const fs::path& path)
{
music = std::shared_ptr<Mix_Music>(Mix_LoadMUS(path.c_str()), Mix_FreeMusic);
if (music.get() == nullptr)
{
Log::sdl_error();
} }
} }
void Music::play(int loops)
{
Mix_PlayMusic(music.get(), loops);
}

View File

@ -13,91 +13,163 @@
#include <regex> #include <regex>
#include <map> #include <map>
#include <functional> #include <functional>
#include <algorithm>
#include "SDL.h" #include "SDL.h"
#include "SDL_mixer.h" #include "SDL_mixer.h"
#include "filesystem.hpp" #include "filesystem.hpp"
#include "Node.hpp" #include "Log.hpp"
#include "Animation.hpp"
#include "extension.hpp"
struct BGM : Node namespace sb::audio
{ {
Mix_Music* music; /*!
float volume = 1.0f; * Load audio from an OGG or WAV file and play it on multiple channels.
*
BGM(); * This class interfaces with SDL mixer's API and its `Mix_Chunk` audio data struct.
BGM(Node*); *
BGM(Node*, const fs::path&); * There are some differences between how `Mix_Chunk` and `Mix_Music` can be used with the API, most notably that multiple chunks can be playing on
BGM(const fs::path&); * multiple channels simultaneously, but only one music object can be playing at a time. This is most likely because `Mix_Music` objects are streamed
void load_music(const fs::path&); * and `Mix_Chunk` are loaded into memory, but I haven't verified that by looking at the internal code.
void set_volume(float); */
void play(int = -1); class Chunk
~BGM();
};
struct SoundEffect : Node
{
Mix_Chunk* chunk = nullptr;
int channel, loops = 0;
float volume = 1.0f;
/* The cast is required so that the overloaded play function without arguments will be selected. */
Animation play_animation = Animation(std::bind(static_cast<void(SoundEffect::*)()>(&SoundEffect::play), this));
SoundEffect(Node*);
SoundEffect();
SoundEffect(Node*, const fs::path&);
SoundEffect(const fs::path&);
void load_chunk(const fs::path&);
void play(float);
void play();
void play_after(float);
void set_volume(float);
void set_loop_count(int);
void set_loop_forever();
bool playing(bool = false) const;
void update(float timestamp);
~SoundEffect();
};
struct Audio : Node
{
std::map<std::string, SoundEffect> sfx;
std::map<std::string, BGM> bgm;
Audio(Node*);
void load_sfx();
void load_bgm();
void update(float timestamp);
template <typename T>
void load_directory(const fs::path& root, std::map<std::string, T>& storage, Node* parent = nullptr)
{ {
SDL_Log("looking for audio files in %s", root.c_str());
if (fs::exists(root))
{
for (const fs::path& path : sb::glob(root / ".*"))
{
std::regex pattern("([^.]+).*$");
std::smatch match;
std::string basename = path.filename();
if (std::regex_match(basename, match, pattern))
{
std::string key = match[1].str();
SDL_Log("loading %s as %s", path.c_str(), key.c_str());
storage.try_emplace(key, parent, path);
}
}
}
}
private:
std::shared_ptr<Mix_Chunk> chunk;
/* -1 means loop forever, any other value is the number of times to loop */
int loops = 0;
/*!
* Convert floating point volume between 0.0 and 1.0 to SDL 8-bit unsigned integer volume between 0 and `MIX_MAX_VOLUME`.
*
* @param volume Volume between 0.0 and 1.0
*/
static std::uint8_t convert_volume(float volume);
public:
/*!
* Create an empty audio chunk.
*/
Chunk() = default;
/*!
* Create an audio chunk and load it with data from an OGG or WAV file at the given path. If the given path does not contain loadable data,
* a warning will be printed, and an empty chunk will be created.
*
* This will call Chunk::load(const fs::path&) automatically.
*
* @param path path to an OGG or WAV file
*/
Chunk(const fs::path& path);
/*!
* Load audio data from an OGG or WAV file at the given path, replacing any existing audio data in this object. If the given path does not contain
* loadable data, a warning will be printed, and existing audio data will not be overwritten.
*
* @param path path to an OGG or WAV file
*/
void load(const fs::path& path);
/*!
* @return the volume of the audio chunk in the range 0.0 - 1.0
*/
float volume() const;
/*!
* @param level set the volume of the chunk to a value between 0.0 and 1.0
*/
void volume(float level);
/*!
* Set the volume of all channels currently playing the audio chunk to a given level. This does not modify the volume of the chunk itself.
*
* @param level Volume between 0.0 and 1.0
*/
void channel_volume(float level);
/*!
* Set audio to loop forever.
*/
void loop();
/*!
* @param count Number of times audio should loop when played
*/
void loop(int count);
/*!
* Play the audio data loaded into this object once on the first available free channel. Optionally fade in playback over a given amount of time
* in seconds.
*
* This always plays the audio from the beginning even if the audio is paused. To resume instead, use Chunk::resume(). Any audio chunks currently
* paused will be stopped.
*
* If the audio is playing already, however, it will continue to play on that channel, and it will be played again from the beginning on another
* channel.
*
* @param fade Length of fade in in seconds
* @param channel The SDL mixer channel to play the sound on. A value of -1 means choose a channel automatically.
* @return The channel assigned to the audio chunk
*/
int play(float fade = 0.0f, int channel = -1);
/*!
* Stop playback of this audio chunk on all channels it is playing on. Optionally fade out over the given amount of time in seconds.
*
* @param fade length of fade out in seconds
*/
void stop(float fade = 0.0f);
/*!
* Pause audio chunk on all channels it is playing on.
*/
void pause();
/*!
* Resume audio chunk on all channels it is playing on.
*/
void resume();
/*!
* @return True if the audio is playing on any channel, false otherwise. Note that paused or fading audio is considered playing.
*/
bool playing() const;
/*!
* @return True if the audio is paused on any channels, false otherwise.
*/
bool paused() const;
/*!
* @return True if the audio is fading in or out on any channels, false otherwise.
*/
bool fading() const;
};
/*!
* Load audio from an OGG or WAV file and play it on a single channel dedicated to music. Only one music object can be playing at a time.
*
* This class interfaces with SDL mixer's API and Mix_Music audio data pointer.
*
* There are some differences between how Mix_Chunk and Mix_Music can be used with the API, most notably that multiple chunks can be playing on
* multiple channels simultaneously, but only one music object can be playing at a time. This is most likely because Mix_Music objects are streamed
* and Mix_Chunk are loaded into memory, but I haven't verified that by looking at the internal code.
*/
class Music
{
private:
std::shared_ptr<Mix_Music> music;
public:
Music() = default;
Music(const fs::path& path);
void load(const fs::path& path);
void play(int loops = -1);
};
}; };
#include "Box.hpp"
#include "Configuration.hpp"
#include "Display.hpp"

View File

@ -187,9 +187,8 @@ Game::Game()
{ {
SDL_Log("initialized SDL mixer %d.%d.%d", SDL_MIXER_MAJOR_VERSION, SDL_MIXER_MINOR_VERSION, SDL_MIXER_PATCHLEVEL); SDL_Log("initialized SDL mixer %d.%d.%d", SDL_MIXER_MAJOR_VERSION, SDL_MIXER_MINOR_VERSION, SDL_MIXER_PATCHLEVEL);
} }
// if (Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, MIX_DEFAULT_FORMAT, if (Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, MIX_DEFAULT_FORMAT, MIX_DEFAULT_CHANNELS, 1024) < 0)
// MIX_DEFAULT_CHANNELS, 1024) < 0) // if (Mix_OpenAudio(11025, AUDIO_U8, MIX_DEFAULT_CHANNELS, 2048) < 0)
if (Mix_OpenAudio(11025, AUDIO_U8, MIX_DEFAULT_CHANNELS, 2048) < 0)
{ {
sb::Log::sdl_error("Could not set up audio"); sb::Log::sdl_error("Could not set up audio");
} }
@ -201,8 +200,6 @@ Game::Game()
message << "Found audio capture device " << ii << ": " << SDL_GetAudioDeviceName(ii, SDL_TRUE); message << "Found audio capture device " << ii << ": " << SDL_GetAudioDeviceName(ii, SDL_TRUE);
sb::Log::log(message); sb::Log::log(message);
} }
audio.load_sfx();
audio.load_bgm();
#if SDL_BYTEORDER == SDL_BIG_ENDIAN #if SDL_BYTEORDER == SDL_BIG_ENDIAN
sb::Log::log("big endian"); sb::Log::log("big endian");
#else #else
@ -539,11 +536,6 @@ Input& Game::get_input()
return input; return input;
} }
Audio& Game::get_audio()
{
return audio;
}
std::shared_ptr<TTF_Font> sb::Game::font() const std::shared_ptr<TTF_Font> sb::Game::font() const
{ {
return _font; return _font;
@ -621,7 +613,6 @@ void Game::frame(float timestamp)
float timestamp_seconds = timestamp / 1000.0f; float timestamp_seconds = timestamp / 1000.0f;
// recorder.update(timestamp_seconds); // recorder.update(timestamp_seconds);
_delegate.dispatch(); _delegate.dispatch();
audio.update(timestamp_seconds);
input.unsuppress_animation.update(timestamp_seconds); input.unsuppress_animation.update(timestamp_seconds);
update(timestamp_seconds); update(timestamp_seconds);
_configuration.update(timestamp_seconds); _configuration.update(timestamp_seconds);
@ -676,7 +667,6 @@ void Game::handle_quit_event(SDL_Event &event)
void Game::quit() void Game::quit()
{ {
/* Unsubscribe all delegate object's subscribers. */ /* Unsubscribe all delegate object's subscribers. */
_delegate.unsubscribe(&audio);
_delegate.unsubscribe(&display); _delegate.unsubscribe(&display);
_delegate.unsubscribe(&input); _delegate.unsubscribe(&input);
_delegate.unsubscribe(this); _delegate.unsubscribe(this);

View File

@ -110,7 +110,6 @@ public:
sb::Display display {this}; sb::Display display {this};
// Recorder recorder {this}; // Recorder recorder {this};
Input input {this}; Input input {this};
Audio audio {this};
std::vector<float> frame_length_history; std::vector<float> frame_length_history;
Game(); Game();
@ -144,7 +143,6 @@ public:
SDL_Renderer* get_renderer(); SDL_Renderer* get_renderer();
const Input& get_input() const; const Input& get_input() const;
Input& get_input(); Input& get_input();
Audio& get_audio();
/*! /*!
* @return shared pointer to the default font pre-loaded at construction * @return shared pointer to the default font pre-loaded at construction

View File

@ -72,10 +72,10 @@ bool sb::Log::gl_errors(const std::string& heading)
return error_logged; return error_logged;
} }
std::ostringstream sb::Log::sdl_error(const std::string& message) std::ostringstream sb::Log::sdl_error(const std::string& message, const Level level)
{ {
std::ostringstream full_message; std::ostringstream full_message;
full_message << message << " " << SDL_GetError(); full_message << message << " " << SDL_GetError();
log(full_message, Level::ERROR); log(full_message, level);
return full_message; return full_message;
} }

View File

@ -80,13 +80,15 @@ namespace sb
static bool gl_errors(const std::string& heading = ""); static bool gl_errors(const std::string& heading = "");
/*! /*!
* Log a message, adding the results of `SDL_GetError` to the end of the message. Should be used to add more information to * Log a message, adding the results of `SDL_GetError` to the end of the message. This should be used to log a message after an
* an error statement when the error happened in SDL. The priority level will always be `Level::ERROR`. * SDL API function returns an error value because SDL functions are written to set the value of SDL_GetError in that case. The
* priority level will be `Level::ERROR` unless a different level is specified.
* *
* @param message text to log before the SDL error is appended * @param message text to log before the SDL error is appended
* @param level message priority level
* @return a string stream containing the full message * @return a string stream containing the full message
*/ */
static std::ostringstream sdl_error(const std::string& message = ""); static std::ostringstream sdl_error(const std::string& message = "", const Level level = ERROR);
}; };

View File

@ -180,23 +180,22 @@ namespace sb
std::vector<sb::Texture>& textures(); std::vector<sb::Texture>& textures();
/*! /*!
* Get a constant reference to the texture at the given index. If no index is given, get the texture at index 0. If * Get a constant reference to the texture at the given index. If there are no textures, an exception will be thrown.
* there are no textures, an exception will be thrown.
* *
* @param index index of texture to get * @param index index of texture to get
*/ */
const sb::Texture& texture(int index = 0) const; const sb::Texture& texture(int index) const;
/*! /*!
* Get the texture at the given index. If no index is given, get the texture at index 0. If there are no textures, * Get the texture at the given index. If there are no textures, an exception will be thrown.
* an exception will be thrown.
* *
* @param index index of texture to get * @param index index of texture to get
*/ */
sb::Texture& texture(int index = 0); sb::Texture& texture(int index);
/*! /*!
* Add a copy of the given texture to model's list of textures. * Add a copy of the given texture to model's list of textures. Note that the texture object is copied, but the texture data is not copied.
* The texture data is stored as a shared pointer in the texture object, so only the pointer is copied.
* *
* @param texture texture to add to model * @param texture texture to add to model
*/ */

View File

@ -50,6 +50,7 @@ namespace sb
sb::Switch<ReturnType, Arguments...> connection; sb::Switch<ReturnType, Arguments...> connection;
sb::Plane _plane; sb::Plane _plane;
Box box; Box box;
int texture_index = 0;
public: public:
@ -221,7 +222,18 @@ namespace sb
{ {
glUniform1i(texture_flag_uniform.value(), true); glUniform1i(texture_flag_uniform.value(), true);
} }
_plane.texture().bind();
/* Determine texture index by checking the state of the pad and the amount of available textures. If there is more than 1 texture,
* the texture will correspond with the state. */
if (connection && _plane.textures().size() > 1)
{
texture_index = 1;
}
else
{
texture_index = 0;
}
_plane.texture(texture_index).bind();
} }
else if (texture_flag_uniform.has_value()) else if (texture_flag_uniform.has_value())
{ {
@ -245,6 +257,24 @@ namespace sb
return connection.flip(args...); return connection.flip(args...);
} }
/*!
* @return the state of the pad
*/
bool pressed() const
{
return connection;
}
/*!
* This will not trigger the callback if any is set. To do that, use Pad::press(Arguments...).
*
* @param state true or false to set the pad state
*/
void state(bool pressed)
{
connection = pressed;
}
/*! /*!
* @return size in bytes of the pad object's plane object * @return size in bytes of the pad object's plane object
*/ */

View File

@ -19,7 +19,7 @@
namespace sb namespace sb
{ {
/*! /*!
* A Sprite is a container for an sb::Plane object that resets and stores scale, translation, and rotation matrices every time they are set * A Sprite is a wrapper around an sb::Plane object that resets and stores scale, translation, and rotation matrices every time they are set
* and combines them automatically when getting the full transformation. This allows those transformations to be set repeatedly without having * and combines them automatically when getting the full transformation. This allows those transformations to be set repeatedly without having
* to call sb::Plane::untransform() after each set. In the case of translation, there is also a move function that allows the translation to be * to call sb::Plane::untransform() after each set. In the case of translation, there is also a move function that allows the translation to be
* modified without resetting, so the sprite can move relative amounts. * modified without resetting, so the sprite can move relative amounts.
@ -44,8 +44,8 @@ namespace sb
public: public:
/*! /*!
* Construct an instance of Sprite using an existing plane object. The plane will be copied into the Sprite, so further edits will * Construct an instance of Sprite using an existing plane object. The plane will be copied into the Sprite, so further edits must
* need to go through the Sprite class. * be made using the Sprite class.
* *
* @param plane flat model of the sprite * @param plane flat model of the sprite
*/ */
@ -63,7 +63,7 @@ namespace sb
/*! /*!
* Construct a Sprite with a default constructed sb::Plane and attach a list of textures to the plane. Each texture is a frame of the * Construct a Sprite with a default constructed sb::Plane and attach a list of textures to the plane. Each texture is a frame of the
* sprite's animation. The texture is the 2D graphic that displays in the sprite object's location. * sprite's animation. The texture is the 2D graphic drawn at the sprite's location.
* *
* @param textures list of textures * @param textures list of textures
* @param scale amount to scale * @param scale amount to scale
@ -77,8 +77,8 @@ namespace sb
} }
/*! /*!
* Construct a Sprite with a default constructed sb::Plane and give the plane a texture. The texture is the 2D graphic that displays * Construct a Sprite with a default constructed sb::Plane and give the plane a texture. The texture is the 2D graphic drawn at the
* in the sprite object's location. * sprite's location.
* *
* @param texture sprite's 2D graphic * @param texture sprite's 2D graphic
* @param scale amount to scale * @param scale amount to scale
@ -128,7 +128,7 @@ namespace sb
} }
/*! /*!
* Add a new texture to the sprite's plane object from a path to an image file which will converted into a new texture. * Add a new texture to the sprite's plane object from a path to an image file which will be converted into a new texture.
* *
* The texture is loaded into GPU memory if the GL context is active. Otherwise, the path is just attached to the texture, * The texture is loaded into GPU memory if the GL context is active. Otherwise, the path is just attached to the texture,
* and it must be loaded with a call to Sprite::load. * and it must be loaded with a call to Sprite::load.
@ -167,12 +167,10 @@ namespace sb
/*! /*!
* @param index set the object's texture index * @param index set the object's texture index
* @return constant reference to the texture at the given index
*/ */
const sb::Texture& texture_index(int index) void texture_index(int index)
{ {
_texture_index = index; _texture_index = index;
return texture();
} }
/*! /*!

View File

@ -107,8 +107,8 @@ void Text::refresh()
/* Generate texture and create storage. Load pixels from the text rendering surface into the texture. The texture object will handle /* Generate texture and create storage. Load pixels from the text rendering surface into the texture. The texture object will handle
* destroying the previous texture. The texture object is optimized so that it won't reallocate pixel memory if it was previously * destroying the previous texture. The texture object is optimized so that it won't reallocate pixel memory if it was previously
* generated at the same size. */ * generated at the same size. */
texture().generate({flipped->w, flipped->h}, GL_RGBA8, _scaling_quality); texture(0).generate({flipped->w, flipped->h}, GL_RGBA8, _scaling_quality);
texture().load(flipped.get()); texture(0).load(flipped.get());
} }
} }
} }

View File

@ -157,11 +157,11 @@ namespace sb
* generated, using Texture::generate() or Texture::generate(glm::vec2, GLenum, GLint), otherwise an exception will be * generated, using Texture::generate() or Texture::generate(glm::vec2, GLenum, GLint), otherwise an exception will be
* thrown. * thrown.
* *
* From the OpenGL manual:
*
* > While a texture is bound, GL operations on the target to which it is bound affect the bound texture, and queries of * > While a texture is bound, GL operations on the target to which it is bound affect the bound texture, and queries of
* > the target to which it is bound return state from the bound texture. In effect, the texture targets become aliases for * > the target to which it is bound return state from the bound texture. In effect, the texture targets become aliases for
* > the textures currently bound to them. * > the textures currently bound to them.
* >
* > - OpenGL Manual
*/ */
void bind() const override; void bind() const override;