- restore video recording
- remove sdl context - configurable audio device settings - deprecate node - move audio recording input handling to game object
This commit is contained in:
parent
71ff65d588
commit
0e4f06d779
|
@ -1,3 +1,13 @@
|
|||
/* ✨ +------------------------------------------------------+
|
||||
____/ \____ ✨/| Open source game framework licensed to freely use, |
|
||||
✨\ / / | copy, and modify. Created for 🌠dank.game🌠 |
|
||||
+--\ . . /--+ | |
|
||||
| ~/ ︶ \👍| | 🌐 https://open.shampoo.ooo/shampoo/spacebox |
|
||||
| ~~~🌊~~~~🌊~ | +------------------------------------------------------+
|
||||
| SPACE 🪐🅱 OX | /
|
||||
| 🌊 ~ ~~~~ ~~ |/
|
||||
+-------------*/
|
||||
|
||||
#include "Animation.hpp"
|
||||
|
||||
Animation::Animation()
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
/* +------------------------------------------------------+
|
||||
____/ \____ /| - Open source game framework licensed to freely use, |
|
||||
\ / / | copy, modify and sell without restriction |
|
||||
+--\ ^__^ /--+ | |
|
||||
| ~/ \~ | | - created for <https://foam.shampoo.ooo> |
|
||||
| ~~~~~~~~~~~~ | +------------------------------------------------------+
|
||||
| SPACE ~~~~~ | /
|
||||
| ~~~~~~~ BOX |/
|
||||
/* ✨ +------------------------------------------------------+
|
||||
____/ \____ ✨/| Open source game framework licensed to freely use, |
|
||||
✨\ / / | copy, and modify. Created for 🌠dank.game🌠 |
|
||||
+--\ . . /--+ | |
|
||||
| ~/ ︶ \👍| | 🌐 https://open.shampoo.ooo/shampoo/spacebox |
|
||||
| ~~~🌊~~~~🌊~ | +------------------------------------------------------+
|
||||
| SPACE 🪐🅱 OX | /
|
||||
| 🌊 ~ ~~~~ ~~ |/
|
||||
+-------------*/
|
||||
|
||||
#pragma once
|
||||
|
@ -57,8 +57,8 @@ public:
|
|||
void frame_length(float length);
|
||||
|
||||
/*!
|
||||
* Turn the play state to on, causing the animation's callback to run once every frame length. If a delay is given, wait before running. If
|
||||
* the play_once flag is set to true, only play the callback once after the delay.
|
||||
* Turn the play state to on, causing the animation's callback to run once every frame length. If a delay is given, wait before
|
||||
* running. If the play_once flag is set to true, only play the callback once after the delay.
|
||||
*
|
||||
* @param delay Amount of seconds to delay before running
|
||||
* @param play_once If true, only run the callback once instead of once every frame length
|
||||
|
@ -91,8 +91,8 @@ public:
|
|||
/*!
|
||||
* Update the timer and check the function's return value to determine whether a new frame of the animation should be produced.
|
||||
*
|
||||
* This will run the callback automatically if it is stored in this object, but the ability to store the callback in this object is deprecated and
|
||||
* will be removed soon.
|
||||
* This will run the callback automatically if it is stored in this object, but the ability to store the callback in this object is
|
||||
* deprecated and will be removed soon.
|
||||
*
|
||||
* @param timestamp Seconds since the program has started, which can be obtained from Game::update(float)
|
||||
* @return True if the next frame of animation should be triggered, false otherwise
|
||||
|
|
|
@ -19,7 +19,7 @@ void Configuration::set_defaults()
|
|||
{
|
||||
config["keys"] = {
|
||||
{"record", {"CTRL", "SHIFT", "i"}},
|
||||
{"save-current-stash", {"CTRL", "SHIFT", "v"}},
|
||||
{"save current stash", {"CTRL", "SHIFT", "v"}},
|
||||
{"screenshot", {"CTRL", "i"}},
|
||||
{"action", "space"},
|
||||
{"up", "up"},
|
||||
|
@ -43,7 +43,7 @@ void Configuration::set_defaults()
|
|||
{"sdl delay", 6},
|
||||
{"title", "[SPACEBOX]"},
|
||||
{"debug", false},
|
||||
{"show-cursor", false},
|
||||
{"show cursor", false},
|
||||
{"render-test-spacing", 2},
|
||||
{"render driver", "opengl"},
|
||||
{"fluid resize", false},
|
||||
|
@ -55,33 +55,35 @@ void Configuration::set_defaults()
|
|||
};
|
||||
config["audio"] = {
|
||||
{"default-sfx-root", "resource/sfx"},
|
||||
{"default-bgm-root", "resource/bgm"}
|
||||
{"default-bgm-root", "resource/bgm"},
|
||||
{"frequency", 48000},
|
||||
{"chunk size", 2048}
|
||||
};
|
||||
config["gl"] = {
|
||||
{"depth-size", 16},
|
||||
{"red-size", 8},
|
||||
{"green-size", 8},
|
||||
{"blue-size", 8},
|
||||
{"share-with-current-context", true},
|
||||
{"double-buffer", true},
|
||||
{"major-version", 3},
|
||||
{"minor-version", 2}
|
||||
{"depth size", 16},
|
||||
{"red size", 8},
|
||||
{"green size", 8},
|
||||
{"blue size", 8},
|
||||
{"share with current context", true},
|
||||
{"double buffer", true},
|
||||
{"major version", 3},
|
||||
{"minor version", 2}
|
||||
},
|
||||
config["recording"] = {
|
||||
{"enabled", false},
|
||||
{"video frame length", 60.0f},
|
||||
{"screenshot-prefix", "screenshot-"},
|
||||
{"screenshot-extension", ".png"},
|
||||
{"screenshot-zfill", 5},
|
||||
{"screenshot-directory", "."},
|
||||
{"gif-frame-length", 0.1f},
|
||||
{"video-directory", "."},
|
||||
{"write-mp4", false},
|
||||
{"max-stash-length", 5.0f},
|
||||
{"max-in-game-stashes", 3},
|
||||
{"max-video-stashes", 40},
|
||||
{"max-video-memory", 1000},
|
||||
{"mp4-pixel-format", "yuv444p"}
|
||||
{"video frame length", 1.0f / 60.0f},
|
||||
{"screenshot prefix", "screenshot-"},
|
||||
{"screenshot extension", ".png"},
|
||||
{"screenshot zfill", 5},
|
||||
{"screenshot directory", "."},
|
||||
{"gif frame length", 0.1f},
|
||||
{"video directory", "."},
|
||||
{"write mp4", false},
|
||||
{"max stash length", 5.0f},
|
||||
{"max in game stashes", 3},
|
||||
{"max video stashes", 40},
|
||||
{"max video memory", 1000},
|
||||
{"mp4 pixel format", "yuv444p"}
|
||||
};
|
||||
config["animation"] = {
|
||||
{"all frames frameset name", "all"}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
/* +------------------------------------------------------+
|
||||
____/ \____ /| - Open source game framework licensed to freely use, |
|
||||
\ / / | copy, modify and sell without restriction |
|
||||
+--\ ^__^ /--+ | |
|
||||
| ~/ \~ | | - created for <https://foam.shampoo.ooo> |
|
||||
| ~~~~~~~~~~~~ | +------------------------------------------------------+
|
||||
| SPACE ~~~~~ | /
|
||||
| ~~~~~~~ BOX |/
|
||||
/* ✨ +------------------------------------------------------+
|
||||
____/ \____ ✨/| Open source game framework licensed to freely use, |
|
||||
✨\ / / | copy, and modify. Created for 🌠dank.game🌠 |
|
||||
+--\ . . /--+ | |
|
||||
| ~/ ︶ \👍| | 🌐 https://open.shampoo.ooo/shampoo/spacebox |
|
||||
| ~~~🌊~~~~🌊~ | +------------------------------------------------------+
|
||||
| SPACE 🪐🅱 OX | /
|
||||
| 🌊 ~ ~~~~ ~~ |/
|
||||
+-------------*/
|
||||
|
||||
#include "Delegate.hpp"
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
/* +------------------------------------------------------+
|
||||
____/ \____ /| - Open source game framework licensed to freely use, |
|
||||
\ / / | copy, modify and sell without restriction |
|
||||
+--\ ^__^ /--+ | |
|
||||
| ~/ \~ | | - created for <https://foam.shampoo.ooo> |
|
||||
| ~~~~~~~~~~~~ | +------------------------------------------------------+
|
||||
| SPACE ~~~~~ | /
|
||||
| ~~~~~~~ BOX |/
|
||||
/* ✨ +------------------------------------------------------+
|
||||
____/ \____ ✨/| Open source game framework licensed to freely use, |
|
||||
✨\ / / | copy, and modify. Created for 🌠dank.game🌠 |
|
||||
+--\ . . /--+ | |
|
||||
| ~/ ︶ \👍| | 🌐 https://open.shampoo.ooo/shampoo/spacebox |
|
||||
| ~~~🌊~~~~🌊~ | +------------------------------------------------------+
|
||||
| SPACE 🪐🅱 OX | /
|
||||
| 🌊 ~ ~~~~ ~~ |/
|
||||
+-------------*/
|
||||
|
||||
#pragma once
|
||||
|
|
|
@ -73,45 +73,33 @@ Uint32 sb::Display::pixel_format(int display_index) const
|
|||
}
|
||||
}
|
||||
|
||||
/* Fill the supplied, pre-allocated buffer with 32-bit pixels (8 bits per component) from the GL
|
||||
* read buffer if in GL context or from the SDL renderer if in SDL context */
|
||||
void sb::Display::screen_pixels(unsigned char* pixels, int w, int h, int x, int y) const
|
||||
{
|
||||
if (get_root()->is_gl_context)
|
||||
GLenum format;
|
||||
|
||||
/* GL_BGRA is not defined in Open GL ES (some info available at
|
||||
* https://community.khronos.org/t/why-opengles-2-spec-doesnt-support-bgra-texture-format/72853) */
|
||||
#if !defined(__EMSCRIPTEN__) && !defined(__ANDROID__) && !defined(ANDROID)
|
||||
if constexpr (SDL_BYTEORDER == SDL_BIG_ENDIAN)
|
||||
{
|
||||
GLenum format;
|
||||
|
||||
/* GL_BGRA is not defined in Open GL ES (some info available at
|
||||
* https://community.khronos.org/t/why-opengles-2-spec-doesnt-support-bgra-texture-format/72853) */
|
||||
#if !defined(__EMSCRIPTEN__) && !defined(__ANDROID__) && !defined(ANDROID)
|
||||
if constexpr (SDL_BYTEORDER == SDL_BIG_ENDIAN)
|
||||
{
|
||||
format = GL_BGRA;
|
||||
}
|
||||
else
|
||||
{
|
||||
#endif
|
||||
format = GL_RGBA;
|
||||
#if !defined(__EMSCRIPTEN__) && !defined(__ANDROID__) && !defined(ANDROID)
|
||||
}
|
||||
#endif
|
||||
|
||||
glReadBuffer(GL_FRONT);
|
||||
glReadPixels(x, y, w, h, format, GL_UNSIGNED_BYTE, pixels);
|
||||
/* Debug statement showing the framebuffer status and first pixel read */
|
||||
std::ostringstream message;
|
||||
message << "read framebuffer status: " << glCheckFramebufferStatus(GL_READ_FRAMEBUFFER) <<
|
||||
", draw framebuffer status: " << glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) <<
|
||||
", upper left screen pixel value read: " << pixels[0] << " " << pixels[1] << " " << pixels[2] << " " << pixels[3];
|
||||
sb::Log::log(message, sb::Log::DEBUG);
|
||||
format = GL_BGRA;
|
||||
}
|
||||
else
|
||||
{
|
||||
SDL_Renderer* renderer = const_cast<SDL_Renderer*>(get_renderer());
|
||||
SDL_SetRenderTarget(renderer, nullptr);
|
||||
SDL_RenderPresent(renderer);
|
||||
SDL_RenderReadPixels(renderer, nullptr, SDL_PIXELFORMAT_RGBA32, pixels, bpp / 8 * w);
|
||||
#endif
|
||||
format = GL_RGBA;
|
||||
#if !defined(__EMSCRIPTEN__) && !defined(__ANDROID__) && !defined(ANDROID)
|
||||
}
|
||||
#endif
|
||||
|
||||
glReadBuffer(GL_FRONT);
|
||||
glReadPixels(x, y, w, h, format, GL_UNSIGNED_BYTE, pixels);
|
||||
/* Debug statement showing the framebuffer status and first pixel read */
|
||||
std::ostringstream message;
|
||||
message << "read framebuffer status: " << glCheckFramebufferStatus(GL_READ_FRAMEBUFFER) <<
|
||||
", draw framebuffer status: " << glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) <<
|
||||
", upper left screen pixel value read: " << pixels[0] << " " << pixels[1] << " " << pixels[2] << " " << pixels[3];
|
||||
sb::Log::log(message, sb::Log::VERBOSE);
|
||||
}
|
||||
|
||||
SDL_Surface* sb::Display::screen_surface() const
|
||||
|
@ -119,7 +107,7 @@ SDL_Surface* sb::Display::screen_surface() const
|
|||
glm::ivec2 size = window_size();
|
||||
unsigned char* pixels = new unsigned char[bpp / 8 * size.x * size.y];
|
||||
screen_pixels(pixels, size.x, size.y);
|
||||
SDL_Surface* surface = screen_surface_from_pixels(pixels, get_root()->is_gl_context);
|
||||
SDL_Surface* surface = screen_surface_from_pixels(pixels);
|
||||
delete[] pixels;
|
||||
return surface;
|
||||
}
|
||||
|
|
|
@ -44,9 +44,21 @@ namespace sb
|
|||
Box window_box(bool = false) const;
|
||||
Box ndc_subsection(const Box&) const;
|
||||
Box ndc_to_pixel(const Box&) const;
|
||||
void screen_pixels(unsigned char*, int, int, int = 0, int = 0) const;
|
||||
|
||||
/*!
|
||||
* Fill the supplied, pre-allocated buffer with 32-bit pixels (8 bits per component) from the GL read buffer. This buffer of
|
||||
* unsigned 8-bit values must be large enough to hold w x h x 32-bits of data
|
||||
*
|
||||
* @param pixels Pre-allocated, unsigned 8-bit buffer that will be filled with pixel color data
|
||||
* @param w Size in width of the region to read from the GL read buffer
|
||||
* @param h Size in height of the region to read from the GL read buffer
|
||||
* @param x X position of the corner to start reading from in the GL read buffer
|
||||
* @param y Y position of the corner to start reading from in the GL read buffer
|
||||
*/
|
||||
void screen_pixels(unsigned char* pixels, int w, int h, int x = 0, int y = 0) const;
|
||||
|
||||
SDL_Surface* screen_surface() const;
|
||||
SDL_Surface* screen_surface_from_pixels(unsigned char*, bool) const;
|
||||
SDL_Surface* screen_surface_from_pixels(unsigned char* pixels, bool flip = true) const;
|
||||
|
||||
/*!
|
||||
* Respond to full screen requests and window resize events. If fluid resize is enabled in the configuration, respond to
|
||||
|
|
286
src/Game.cpp
286
src/Game.cpp
|
@ -1,11 +1,11 @@
|
|||
/* +------------------------------------------------------+
|
||||
____/ \____ /| - Open source game framework licensed to freely use, |
|
||||
\ / / | copy, modify and sell without restriction |
|
||||
+--\ ^__^ /--+ | |
|
||||
| ~/ \~ | | - created for <https://foam.shampoo.ooo> |
|
||||
| ~~~~~~~~~~~~ | +------------------------------------------------------+
|
||||
| SPACE ~~~~~ | /
|
||||
| ~~~~~~~ BOX |/
|
||||
/* ✨ +------------------------------------------------------+
|
||||
____/ \____ ✨/| Open source game framework licensed to freely use, |
|
||||
✨\ / / | copy, and modify. Created for 🌠dank.game🌠 |
|
||||
+--\ . . /--+ | |
|
||||
| ~/ ︶ \👍| | 🌐 https://open.shampoo.ooo/shampoo/spacebox |
|
||||
| ~~~🌊~~~~🌊~ | +------------------------------------------------------+
|
||||
| SPACE 🪐🅱 OX | /
|
||||
| 🌊 ~ ~~~~ ~~ |/
|
||||
+-------------*/
|
||||
|
||||
#include "Game.hpp"
|
||||
|
@ -46,13 +46,9 @@ Game::Game(std::initializer_list<std::string> configuration_merge)
|
|||
}
|
||||
|
||||
/* If recording is enabled by configuration, activate it. */
|
||||
if (configuration()["recording"]["enabled"])
|
||||
if (configuration()("recording", "enabled"))
|
||||
{
|
||||
/* Recorder object disabled because of a memory leak
|
||||
*
|
||||
* recorder.animation.play()
|
||||
*/
|
||||
activate();
|
||||
recorder.animation.play();
|
||||
}
|
||||
|
||||
/* Log the current working directory as seen by std::filesystem */
|
||||
|
@ -68,7 +64,8 @@ Game::Game(std::initializer_list<std::string> configuration_merge)
|
|||
log_message = std::ostringstream();
|
||||
log_message << "Using Android SDK version " << SDL_GetAndroidSDKVersion() << std::endl;
|
||||
log_message << "SDL_AndroidGetInternalStoragePath() is " << SDL_AndroidGetInternalStoragePath() << std::endl;
|
||||
log_message << "SDL_AndroidGetExternalStorageState() is " << SDL_AndroidGetExternalStorageState() << " (1=read, 2=write, 3=r/w)" << std::endl;
|
||||
log_message << "SDL_AndroidGetExternalStorageState() is " << SDL_AndroidGetExternalStorageState() <<
|
||||
" (1=read, 2=write, 3=r/w)" << std::endl;
|
||||
log_message << "SDL_AndroidGetExternalStoragePath() is " << SDL_AndroidGetExternalStoragePath();
|
||||
sb::Log::log(log_message);
|
||||
#endif
|
||||
|
@ -78,14 +75,15 @@ Game::Game(std::initializer_list<std::string> configuration_merge)
|
|||
log_message << std::setw(4) << configuration()() << std::endl;
|
||||
sb::Log::log(log_message, sb::Log::DEBUG);
|
||||
|
||||
/* Tell SDL which render driver you will be requesting when calling SDL_CreateRenderer */
|
||||
/* Tell SDL which render driver you will be requesting (not sure whether this only affects SDL_ GL */
|
||||
SDL_SetHint(SDL_HINT_RENDER_DRIVER, configuration()["display"]["render driver"].get<std::string>().c_str());
|
||||
|
||||
/* Initialize the buffer of frame lengths which will be used to calculate and display FPS */
|
||||
frame_length_history.reserve(5000);
|
||||
|
||||
/* Subscribe to SDL's quit event */
|
||||
/* Subscribe to events */
|
||||
_delegate.subscribe(&Game::handle_quit_event, this, SDL_QUIT);
|
||||
_delegate.subscribe(&Game::respond, this);
|
||||
|
||||
/* Needed for displaying fullscreen correctly on Linux (?) Also might need SDL_VIDEO_CENTERED (?) */
|
||||
std::string fullscreen_env_assigment = "SDL_VIDEO_X11_LEGACY_FULLSCREEN=0";
|
||||
|
@ -116,67 +114,85 @@ Game::Game(std::initializer_list<std::string> configuration_merge)
|
|||
#endif
|
||||
|
||||
sb::Log::log(log_message.str());
|
||||
glm::ivec2 window_size = configuration()["display"]["dimensions"].get<glm::ivec2>();
|
||||
glm::ivec2 window_size = _configuration("display", "dimensions").get<glm::ivec2>();
|
||||
|
||||
/* Set GL context attributes before creating a window (see SDL_GLattr.html). Don't ask Emscripten of Android for a specific GL context version. */
|
||||
/* Set GL context attributes before creating a window (see SDL_GLattr.html). Don't ask Emscripten or Android for a specific GL
|
||||
* context version. */
|
||||
#if !defined(__EMSCRIPTEN__) && !defined(__ANDROID__) && !defined(ANDROID)
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, configuration()["gl"]["major-version"]);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, configuration()["gl"]["minor-version"]);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, _configuration("gl", "major version"));
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, _configuration("gl", "minor version"));
|
||||
#endif
|
||||
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, configuration()["gl"]["depth-size"]);
|
||||
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, configuration()["gl"]["red-size"]);
|
||||
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, configuration()["gl"]["green-size"]);
|
||||
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, configuration()["gl"]["blue-size"]);
|
||||
SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, configuration()["gl"]["share-with-current-context"]);
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, configuration()["gl"]["double-buffer"]);
|
||||
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, _configuration("gl", "depth size"));
|
||||
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, _configuration("gl", "red size"));
|
||||
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, _configuration("gl", "green size"));
|
||||
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, _configuration("gl", "blue size"));
|
||||
SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, _configuration("gl", "share with current context"));
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, _configuration("gl", "double buffer"));
|
||||
|
||||
#if !defined(__MINGW32__)
|
||||
/* Set the profile to ES so that desktop and web builds are both using the same profile. This should be handled by a configuration
|
||||
* option in the fututre. */
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
|
||||
#else
|
||||
/* Unless on Windows, which may need to be run in compatibility mode */
|
||||
/* Use compatibility mode on Windows because it seemed to be required on one version of Windows tested */
|
||||
#if defined(__MINGW32__)
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY);
|
||||
#endif
|
||||
|
||||
/* Create a window with dimensions set in the config, centered, and flagged to be usable in OpenGL context */
|
||||
Uint32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE;
|
||||
if (configuration()("display", "fullscreen")) flags |= SDL_WINDOW_FULLSCREEN;
|
||||
_window = SDL_CreateWindow(
|
||||
configuration()["display"]["title"].get_ref<const std::string&>().c_str(), SDL_WINDOWPOS_CENTERED,
|
||||
SDL_WINDOWPOS_CENTERED, window_size.x, window_size.y, flags);
|
||||
if (_configuration("display", "fullscreen"))
|
||||
{
|
||||
flags |= SDL_WINDOW_FULLSCREEN;
|
||||
}
|
||||
_window = SDL_CreateWindow(_configuration("display", "title").get_ref<const std::string&>().c_str(), SDL_WINDOWPOS_CENTERED,
|
||||
SDL_WINDOWPOS_CENTERED, window_size.x, window_size.y, flags);
|
||||
if (_window == nullptr)
|
||||
{
|
||||
sb::Log::sdl_error("Could not create window");
|
||||
flag_to_end();
|
||||
}
|
||||
|
||||
/* Create an SDL renderer for clearing the screen to black and for logging renderer properties. Destroy renderer when finished.
|
||||
* Skip this in emscripten because it causes a mouse event bug. */
|
||||
#ifndef __EMSCRIPTEN__
|
||||
if ((renderer = SDL_CreateRenderer(_window, -1, SDL_RENDERER_TARGETTEXTURE | SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC)) == nullptr)
|
||||
|
||||
/* Create a single GL context for the game. This is restrictive since it forces the game to use only one context, so it should
|
||||
* be improved in the future. */
|
||||
if ((glcontext = SDL_GL_CreateContext(window())) == nullptr)
|
||||
{
|
||||
sb::Log::sdl_error("Could not create renderer");
|
||||
sb::Log::sdl_error("Could not get GL context");
|
||||
flag_to_end();
|
||||
}
|
||||
|
||||
/* Clear window to black */
|
||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
SDL_GL_SwapWindow(_window);
|
||||
|
||||
/* Try setting vsync */
|
||||
bool vsync_enabled = configuration()("display", "vsync").get<bool>();
|
||||
if (SDL_GL_SetSwapInterval(static_cast<int>(vsync_enabled)) == 0)
|
||||
{
|
||||
std::ostringstream message;
|
||||
message << "Set vsync to " << vsync_enabled;
|
||||
sb::Log::log(message);
|
||||
}
|
||||
else
|
||||
{
|
||||
int w, h;
|
||||
/* log the renderer resolution */
|
||||
SDL_GetRendererOutputSize(renderer, &w, &h);
|
||||
log_message = std::ostringstream();
|
||||
log_message << "renderer output size is " << w << "x" << h;
|
||||
sb::Log::log(log_message);
|
||||
/* clear screen to black */
|
||||
SDL_SetRenderTarget(renderer, nullptr);
|
||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
|
||||
SDL_RenderClear(renderer);
|
||||
SDL_RenderPresent(renderer);
|
||||
SDL_RenderFlush(renderer);
|
||||
SDL_DestroyRenderer(renderer);
|
||||
sb::Log::log("Setting vysnc is not supported");
|
||||
}
|
||||
|
||||
/* Initialize GLEW for GL function discovery on all platforms except Android */
|
||||
#if !defined(__ANDROID__) && !defined(ANDROID)
|
||||
GLenum error = glewInit();
|
||||
if (error != GLEW_OK)
|
||||
{
|
||||
std::ostringstream message;
|
||||
message << "GLEW could not initialize " << glewGetErrorString(error);
|
||||
sb::Log::log(message, sb::Log::ERR);
|
||||
}
|
||||
#endif
|
||||
SDL_ShowCursor(configuration()["display"]["show-cursor"]);
|
||||
|
||||
/* Log the GL profile that was chosen */
|
||||
log_gl_properties();
|
||||
log_display_mode();
|
||||
|
||||
/* Show or hide cursor depending on the configuration */
|
||||
SDL_ShowCursor(_configuration("display", "show cursor"));
|
||||
|
||||
/* Initialize SDL TTF font library */
|
||||
if (TTF_Init() < 0)
|
||||
{
|
||||
sb::Log::sdl_error("Could not initialize SDL ttf");
|
||||
|
@ -209,7 +225,8 @@ Game::Game(std::initializer_list<std::string> configuration_merge)
|
|||
}
|
||||
|
||||
/* Open the audio device chosen automatically by SDL mixer */
|
||||
if (Mix_OpenAudio(48000, MIX_DEFAULT_FORMAT, MIX_DEFAULT_CHANNELS, 4096) < 0)
|
||||
if (Mix_OpenAudio(_configuration("audio", "frequency"), MIX_DEFAULT_FORMAT, MIX_DEFAULT_CHANNELS,
|
||||
_configuration("audio", "chunk size")) < 0)
|
||||
{
|
||||
sb::Log::sdl_error("Could not set up audio");
|
||||
}
|
||||
|
@ -227,79 +244,6 @@ Game::Game(std::initializer_list<std::string> configuration_merge)
|
|||
last_frame_timestamp = SDL_GetTicks();
|
||||
}
|
||||
|
||||
void Game::load_sdl_context()
|
||||
{
|
||||
if (glcontext != nullptr)
|
||||
{
|
||||
SDL_GL_DeleteContext(glcontext);
|
||||
glcontext = nullptr;
|
||||
}
|
||||
SDL_RendererInfo renderer_info;
|
||||
int render_driver_count = SDL_GetNumRenderDrivers();
|
||||
SDL_Log("Render drivers:");
|
||||
for (int ii = 0; ii < render_driver_count; ii++)
|
||||
{
|
||||
SDL_GetRenderDriverInfo(ii, &renderer_info);
|
||||
log_renderer_info(renderer_info);
|
||||
}
|
||||
if ((renderer = SDL_CreateRenderer(_window, -1, SDL_RENDERER_TARGETTEXTURE | SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC)) == nullptr)
|
||||
{
|
||||
sb::Log::sdl_error("Could not create renderer");
|
||||
flag_to_end();
|
||||
}
|
||||
else
|
||||
{
|
||||
SDL_Log("Current renderer:");
|
||||
SDL_GetRendererInfo(renderer, &renderer_info);
|
||||
log_renderer_info(renderer_info);
|
||||
SDL_Log("Renderer supports the use of render targets? %d", SDL_RenderTargetSupported(renderer));
|
||||
}
|
||||
is_gl_context = false;
|
||||
log_display_mode();
|
||||
}
|
||||
|
||||
void Game::load_gl_context()
|
||||
{
|
||||
if (renderer != nullptr)
|
||||
{
|
||||
SDL_DestroyRenderer(renderer);
|
||||
renderer = nullptr;
|
||||
}
|
||||
if ((glcontext = SDL_GL_CreateContext(window())) == nullptr)
|
||||
{
|
||||
sb::Log::sdl_error("Could not get GL context");
|
||||
flag_to_end();
|
||||
}
|
||||
|
||||
/* Try setting vsync */
|
||||
bool vsync_enabled = configuration()("display", "vsync").get<bool>();
|
||||
if (SDL_GL_SetSwapInterval(static_cast<int>(vsync_enabled)) == 0)
|
||||
{
|
||||
std::ostringstream message;
|
||||
message << "Set vsync to " << vsync_enabled;
|
||||
sb::Log::log(message);
|
||||
}
|
||||
else
|
||||
{
|
||||
sb::Log::log("Setting vysnc is not supported");
|
||||
}
|
||||
|
||||
/* Android does not use GLEW*/
|
||||
#if !defined(__ANDROID__) && !defined(ANDROID)
|
||||
GLenum error = glewInit();
|
||||
if (error != GLEW_OK)
|
||||
{
|
||||
std::ostringstream message;
|
||||
message << "GLEW could not initialize " << glewGetErrorString(error);
|
||||
sb::Log::log(message, sb::Log::ERR);
|
||||
}
|
||||
#endif
|
||||
|
||||
log_gl_properties();
|
||||
is_gl_context = true;
|
||||
log_display_mode();
|
||||
}
|
||||
|
||||
void Game::sdl_log_override(void* userdata, int category, SDL_LogPriority priority, const char* message)
|
||||
{
|
||||
Game* game = static_cast<Game*>(userdata);
|
||||
|
@ -312,7 +256,8 @@ void Game::sdl_log_override(void* userdata, int category, SDL_LogPriority priori
|
|||
|
||||
/* If printing to stdout, print to Android log as well */
|
||||
#if defined(__ANDROID__) || defined(ANDROID)
|
||||
__android_log_print(ANDROID_LOG_VERBOSE, game->configuration()["log"]["short-name"].get_ref<const std::string&>().c_str(), "%s", message);
|
||||
__android_log_print(ANDROID_LOG_VERBOSE, game->configuration()["log"]["short-name"].get_ref<const std::string&>().c_str(),
|
||||
"%s", message);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
@ -419,15 +364,6 @@ bool Game::link_shader(GLuint program) const
|
|||
}
|
||||
}
|
||||
|
||||
void Game::log_renderer_info(SDL_RendererInfo& info)
|
||||
{
|
||||
std::ostringstream message;
|
||||
message << "renderer name: " << info.name << ", flags: " << info.flags << ", texture formats: " <<
|
||||
info.num_texture_formats << ", max texture w: " << info.max_texture_width << ", max texture h: " <<
|
||||
info.max_texture_height;
|
||||
sb::Log::log(message);
|
||||
}
|
||||
|
||||
void Game::log_display_mode() const
|
||||
{
|
||||
SDL_DisplayMode current;
|
||||
|
@ -463,28 +399,28 @@ void Game::log_gl_properties() const
|
|||
int attribute;
|
||||
int status = SDL_GL_GetAttribute(SDL_GL_RED_SIZE, &attribute);
|
||||
if (!status) {
|
||||
message << ", SDL_GL_RED_SIZE: requested " << configuration()["gl"]["red-size"] << ", got " << attribute;
|
||||
message << ", SDL_GL_RED_SIZE: requested " << _configuration("gl", "red size") << ", got " << attribute;
|
||||
} else {
|
||||
sb::Log::sdl_error("Failed to get SDL_GL_RED_SIZE");
|
||||
}
|
||||
|
||||
status = SDL_GL_GetAttribute(SDL_GL_GREEN_SIZE, &attribute);
|
||||
if (!status) {
|
||||
message << ", SDL_GL_GREEN_SIZE: requested " << configuration()["gl"]["green-size"] << ", got " << attribute;
|
||||
message << ", SDL_GL_GREEN_SIZE: requested " << _configuration("gl", "green size") << ", got " << attribute;
|
||||
} else {
|
||||
sb::Log::sdl_error("Failed to get SDL_GL_GREEN_SIZE");
|
||||
}
|
||||
|
||||
status = SDL_GL_GetAttribute(SDL_GL_BLUE_SIZE, &attribute);
|
||||
if (!status) {
|
||||
message << ", SDL_GL_BLUE_SIZE: requested " << configuration()["gl"]["blue-size"] << ", got " << attribute;
|
||||
message << ", SDL_GL_BLUE_SIZE: requested " << _configuration("gl", "blue size") << ", got " << attribute;
|
||||
} else {
|
||||
sb::Log::sdl_error("Failed to get SDL_GL_BLUE_SIZE");
|
||||
}
|
||||
|
||||
status = SDL_GL_GetAttribute(SDL_GL_DEPTH_SIZE, &attribute);
|
||||
if (!status) {
|
||||
message << ", SDL_GL_DEPTH_SIZE: requested " << configuration()["gl"]["depth-size"] << ", got " << attribute;
|
||||
message << ", SDL_GL_DEPTH_SIZE: requested " << _configuration("gl", "depth size") << ", got " << attribute;
|
||||
} else {
|
||||
sb::Log::sdl_error("Failed to get SDL_GL_DEPTH_SIZE");
|
||||
}
|
||||
|
@ -535,16 +471,6 @@ SDL_Window* Game::window()
|
|||
return _window;
|
||||
}
|
||||
|
||||
const SDL_Renderer* Game::get_renderer() const
|
||||
{
|
||||
return renderer;
|
||||
}
|
||||
|
||||
SDL_Renderer* Game::get_renderer()
|
||||
{
|
||||
return renderer;
|
||||
}
|
||||
|
||||
const Input& Game::get_input() const
|
||||
{
|
||||
return input;
|
||||
|
@ -594,7 +520,8 @@ void Game::run()
|
|||
SDL_Log("using emscripten main loop");
|
||||
emscripten_set_main_loop_arg(&loop, this, -1, true);
|
||||
|
||||
/* Other builds request a frame repeatedly until the game quits, delaying for the configured amount of milliseconds between each request. */
|
||||
/* Other builds request a frame repeatedly until the game quits, delaying for the configured amount of milliseconds between each
|
||||
* request. */
|
||||
#else
|
||||
SDL_Log("using standard main loop");
|
||||
while (!done)
|
||||
|
@ -605,6 +532,26 @@ void Game::run()
|
|||
#endif
|
||||
}
|
||||
|
||||
void Game::respond(SDL_Event& event)
|
||||
{
|
||||
if (_delegate.compare(event, "screenshot"))
|
||||
{
|
||||
recorder.capture_screen();
|
||||
}
|
||||
else if (_delegate.compare(event, "record"))
|
||||
{
|
||||
recorder.toggle();
|
||||
}
|
||||
else if (_delegate.compare(event, "save current stash"))
|
||||
{
|
||||
recorder.grab_stash();
|
||||
}
|
||||
else if (_delegate.compare(event, "log video memory size"))
|
||||
{
|
||||
recorder.log_video_memory_size();
|
||||
}
|
||||
}
|
||||
|
||||
void Game::frame(float timestamp)
|
||||
{
|
||||
/* Max framerate is the maximum number of frames to display per second. It is unlimited if the value is -1. */
|
||||
|
@ -626,21 +573,14 @@ void Game::frame(float timestamp)
|
|||
/* Save the timestamp */
|
||||
last_frame_timestamp = timestamp;
|
||||
|
||||
// /* Only build a frame if the last frame was under a second. */
|
||||
// if (last_frame_length < 1000)
|
||||
// {
|
||||
float timestamp_seconds = timestamp / 1000.0f;
|
||||
// recorder.update(timestamp_seconds);
|
||||
_delegate.dispatch();
|
||||
input.unsuppress_animation.update(timestamp_seconds);
|
||||
update(timestamp_seconds);
|
||||
_configuration.update(timestamp_seconds);
|
||||
if (!is_gl_context)
|
||||
{
|
||||
SDL_SetRenderTarget(renderer, nullptr);
|
||||
SDL_RenderPresent(renderer);
|
||||
}
|
||||
// }
|
||||
/* Update game state and render frame (update and draw should be separated into different functions in a future revision to
|
||||
* allow for different timing intervals for each) */
|
||||
float timestamp_seconds = timestamp / 1000.0f;
|
||||
recorder.update(timestamp_seconds);
|
||||
_delegate.dispatch();
|
||||
input.unsuppress_animation.update(timestamp_seconds);
|
||||
update(timestamp_seconds);
|
||||
_configuration.update(timestamp_seconds);
|
||||
|
||||
/* Update frame count per second for verbose log */
|
||||
frame_count_this_second++;
|
||||
|
@ -694,10 +634,6 @@ void Game::quit()
|
|||
{
|
||||
SDL_GL_DeleteContext(glcontext);
|
||||
}
|
||||
if (renderer != nullptr)
|
||||
{
|
||||
SDL_DestroyRenderer(renderer);
|
||||
}
|
||||
if (_window != nullptr)
|
||||
{
|
||||
SDL_DestroyWindow(_window);
|
||||
|
|
88
src/Game.hpp
88
src/Game.hpp
|
@ -1,11 +1,11 @@
|
|||
/* +------------------------------------------------------+
|
||||
____/ \____ /| - Open source game framework licensed to freely use, |
|
||||
\ / / | copy, modify and sell without restriction |
|
||||
+--\ ^__^ /--+ | |
|
||||
| ~/ \~ | | - created for <https://foam.shampoo.ooo> |
|
||||
| ~~~~~~~~~~~~ | +------------------------------------------------------+
|
||||
| SPACE ~~~~~ | /
|
||||
| ~~~~~~~ BOX |/
|
||||
/* ✨ +------------------------------------------------------+
|
||||
____/ \____ ✨/| Open source game framework licensed to freely use, |
|
||||
✨\ / / | copy, and modify. Created for 🌠dank.game🌠 |
|
||||
+--\ . . /--+ | |
|
||||
| ~/ ︶ \👍| | 🌐 https://open.shampoo.ooo/shampoo/spacebox |
|
||||
| ~~~🌊~~~~🌊~ | +------------------------------------------------------+
|
||||
| SPACE 🪐🅱 OX | /
|
||||
| 🌊 ~ ~~~~ ~~ |/
|
||||
+-------------*/
|
||||
|
||||
#pragma once
|
||||
|
@ -99,24 +99,19 @@ public:
|
|||
Game(Game&&) = delete;
|
||||
Game& operator=(Game&&) = delete;
|
||||
|
||||
SDL_Renderer* renderer = nullptr;
|
||||
SDL_GLContext glcontext = nullptr;
|
||||
int frame_count_this_second = 0, last_frame_length, current_frames_per_second = 0;
|
||||
float frame_time_overflow = 0, last_frame_timestamp, last_frame_count_timestamp;
|
||||
bool done = false, show_framerate = true, is_gl_context = true;
|
||||
bool done = false, show_framerate = true;
|
||||
sb::Display display {this};
|
||||
// Recorder recorder {this};
|
||||
Input input {this};
|
||||
std::vector<float> frame_length_history;
|
||||
sb::Recorder recorder {_configuration, display};
|
||||
|
||||
Game(std::initializer_list<std::string> configuration_merge);
|
||||
virtual void reset() { activate(); }
|
||||
void print_frame_length_history();
|
||||
void load_sdl_context();
|
||||
void load_gl_context();
|
||||
GLuint load_shader(const fs::path&, GLenum) const;
|
||||
bool link_shader(GLuint program) const;
|
||||
void log_renderer_info(SDL_RendererInfo&);
|
||||
|
||||
/*!
|
||||
* Write resolution, monitor refresh rate, and pixel format to the log. Taken from SDL_GetCurrentDisplayMode.html
|
||||
|
@ -130,15 +125,53 @@ public:
|
|||
void log_gl_properties() const;
|
||||
|
||||
void log_surface_format(SDL_Surface*, std::string = "surface");
|
||||
|
||||
/*!
|
||||
* @deprecated Global configuration will be removed in favor of a mix of default configuration and user configuration(s)
|
||||
* which can be swapped in and out arbitrarily. For now, it's better to pass the global configuration directly to objects
|
||||
* which need it instead of relying on this public accessor.
|
||||
*/
|
||||
const Configuration& configuration() const;
|
||||
|
||||
/*!
|
||||
* @deprecated Global configuration will be removed in favor of a mix of default configuration and user configuration(s)
|
||||
* which can be swapped in and out arbitrarily. For now, it's better to pass the global configuration directly to objects
|
||||
* which need it instead of relying on this public accessor.
|
||||
*/
|
||||
Configuration& configuration();
|
||||
|
||||
/*!
|
||||
* @deprecated The delegate class will be kept private to the Game object. Instead of subscribing individual objects to specific
|
||||
* input, subscribe and respond to all events by extending Game::response and subscribing new events if necessary.
|
||||
*/
|
||||
const sb::Delegate& delegate() const;
|
||||
|
||||
/*!
|
||||
* @deprecated The delegate class will be kept private to the Game object. Instead of subscribing individual objects to specific
|
||||
* input, subscribe and respond to all events by extending Game::response and subscribing new events if necessary.
|
||||
*/
|
||||
sb::Delegate& delegate();
|
||||
|
||||
/*!
|
||||
* @deprecated The window will be kept private and access will have to come directly from the class.
|
||||
*/
|
||||
const SDL_Window* window() const;
|
||||
|
||||
/*!
|
||||
* @deprecated The window will be kept private and access will have to come directly from the class.
|
||||
*/
|
||||
SDL_Window* window();
|
||||
const SDL_Renderer* get_renderer() const;
|
||||
SDL_Renderer* get_renderer();
|
||||
|
||||
/*!
|
||||
* @deprecated The input class's functionality will be kept private to Game objects, so access will need to go through the Game
|
||||
* class.
|
||||
*/
|
||||
const Input& get_input() const;
|
||||
|
||||
/*!
|
||||
* @deprecated The input class's functionality will be kept private to Game objects, so access will need to go through the Game
|
||||
* class.
|
||||
*/
|
||||
Input& get_input();
|
||||
|
||||
/*!
|
||||
|
@ -147,14 +180,31 @@ public:
|
|||
std::shared_ptr<TTF_Font> font() const;
|
||||
|
||||
/*!
|
||||
* Get a new font object with the given font size. If the font cannot be loaded, the default font will be returned. If there was an error,
|
||||
* the shared pointer will point to `nullptr`.
|
||||
* Get a new font object with the given font size. If the font cannot be loaded, the default font will be returned. If there was an
|
||||
* error, the shared pointer will point to `nullptr`.
|
||||
*
|
||||
* @return shared pointer to the font object created from the TTF font file at the given path
|
||||
*/
|
||||
std::shared_ptr<TTF_Font> font(const fs::path& path, int size) const;
|
||||
|
||||
void run();
|
||||
|
||||
/*!
|
||||
* Dispatch framework events to relevant manager classes like Input, Display, and Recorder.
|
||||
*
|
||||
* Extend this function to respond to custom framework events. Use Delegate::compare to check which event has been fired.
|
||||
*
|
||||
* Framework events like "reset", "quit", and "up", have default input bindings that can be reconfigured or extended to respond
|
||||
* to more bindings.
|
||||
*
|
||||
* @see Input::add_to_key_map
|
||||
* @see Input::load_key_map
|
||||
*
|
||||
* To respond directly to lower-level SDL events like keyboard, mouse, and gamepad input directly, subscribe this function
|
||||
* to a specific type of SDL event using Delegate::add_subscriber.
|
||||
*/
|
||||
virtual void respond(SDL_Event& event);
|
||||
|
||||
void frame(float);
|
||||
void flag_to_end();
|
||||
virtual void update(float timestamp) = 0;
|
||||
|
|
10
src/Node.cpp
10
src/Node.cpp
|
@ -23,16 +23,6 @@ void Node::set_parent(Node* other)
|
|||
parent = other;
|
||||
}
|
||||
|
||||
void Node::set_canvas(SDL_Texture* texture)
|
||||
{
|
||||
canvas = std::shared_ptr<SDL_Texture>(texture, SDL_DestroyTexture);
|
||||
}
|
||||
|
||||
SDL_Texture* Node::get_canvas()
|
||||
{
|
||||
return canvas.get();
|
||||
}
|
||||
|
||||
bool Node::is_active() const
|
||||
{
|
||||
return active;
|
||||
|
|
|
@ -35,6 +35,10 @@ namespace sb
|
|||
class Display;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @deprecated Use an alternative to this class if possible because it will be removed soon. Global access to functionality in
|
||||
* this class should instead go through the Game class directly.
|
||||
*/
|
||||
class Node
|
||||
{
|
||||
|
||||
|
@ -46,8 +50,6 @@ public:
|
|||
virtual void reset() { deactivate(); }
|
||||
virtual void activate() { active = true; }
|
||||
virtual void deactivate() { active = false; }
|
||||
void set_canvas(SDL_Texture*);
|
||||
SDL_Texture* get_canvas();
|
||||
bool is_active() const;
|
||||
const Configuration& configuration() const;
|
||||
Configuration& configuration();
|
||||
|
@ -100,7 +102,6 @@ public:
|
|||
private:
|
||||
|
||||
Node* parent;
|
||||
std::shared_ptr<SDL_Texture> canvas;
|
||||
bool active = true;
|
||||
|
||||
};
|
||||
|
|
202
src/Recorder.cpp
202
src/Recorder.cpp
|
@ -1,68 +1,56 @@
|
|||
/* ✨ +------------------------------------------------------+
|
||||
____/ \____ ✨/| Open source game framework licensed to freely use, |
|
||||
✨\ / / | copy, and modify. Created for 🌠dank.game🌠 |
|
||||
+--\ . . /--+ | |
|
||||
| ~/ ︶ \👍| | 🌐 https://open.shampoo.ooo/shampoo/spacebox |
|
||||
| ~~~🌊~~~~🌊~ | +------------------------------------------------------+
|
||||
| SPACE 🪐🅱 OX | /
|
||||
| 🌊 ~ ~~~~ ~~ |/
|
||||
+-------------*/
|
||||
|
||||
#include "gif-h/gif.h"
|
||||
#include "Game.hpp"
|
||||
#include "extension.hpp"
|
||||
#include "Recorder.hpp"
|
||||
|
||||
using namespace sb;
|
||||
|
||||
/* Create a Recorder instance. Subscribe to command input and set audio callback. */
|
||||
Recorder::Recorder(Node* parent) : Node(parent)
|
||||
Recorder::Recorder(sb::Configuration& configuration, sb::Display& display) : configuration(configuration), display(display)
|
||||
{
|
||||
get_delegate().subscribe(&Recorder::respond, this);
|
||||
// Mix_SetPostMix(Recorder::process_audio, this);
|
||||
Mix_SetPostMix(Recorder::process_audio, this);
|
||||
}
|
||||
|
||||
void Recorder::toggle()
|
||||
{
|
||||
if (is_recording)
|
||||
{
|
||||
end_recording();
|
||||
}
|
||||
else if (!writing_recording)
|
||||
{
|
||||
start_recording();
|
||||
}
|
||||
else
|
||||
{
|
||||
sb::Log::log("Writing in progress, cannot start recording", sb::Log::WARN);
|
||||
}
|
||||
}
|
||||
|
||||
/* Returns length of a recorded video frame in seconds */
|
||||
float Recorder::frame_length()
|
||||
{
|
||||
return configuration()["recording"]["video frame length"];
|
||||
return configuration("recording", "video frame length");
|
||||
}
|
||||
|
||||
/* Handle commands for screenshot, record video and save video */
|
||||
void Recorder::respond(SDL_Event& event)
|
||||
{
|
||||
if (get_delegate().compare(event, "screenshot"))
|
||||
{
|
||||
capture_screen();
|
||||
}
|
||||
else if (get_delegate().compare(event, "record"))
|
||||
{
|
||||
if (is_recording)
|
||||
{
|
||||
end_recording();
|
||||
}
|
||||
else if (!writing_recording)
|
||||
{
|
||||
start_recording();
|
||||
}
|
||||
else
|
||||
{
|
||||
sb::Log::log("Writing in progress, cannot start recording");
|
||||
}
|
||||
}
|
||||
else if (get_delegate().compare(event, "save-current-stash"))
|
||||
{
|
||||
grab_stash();
|
||||
}
|
||||
else if (get_delegate().compare(event, "print-video-memory-size"))
|
||||
{
|
||||
std::ostringstream message;
|
||||
message << "Video memory size is " << get_memory_size() << "MB";
|
||||
sb::Log::log(message);
|
||||
}
|
||||
}
|
||||
|
||||
/* Save the current screen pixels as a PNG in the path specified by the configuration. Parent
|
||||
* directories in the path will be created if necessary. The file name will be auto generated
|
||||
* based on the configuration. An index will be included in the file name based on previous
|
||||
* screenshots found in the output directory. */
|
||||
void Recorder::capture_screen()
|
||||
{
|
||||
const nlohmann::json& config = configuration()();
|
||||
SDL_Surface* surface = get_display().screen_surface();
|
||||
fs::path directory = config["recording"]["screenshot-directory"].get<std::string>();
|
||||
SDL_Surface* surface = display.screen_surface();
|
||||
fs::path directory = configuration("recording", "screenshot directory").get<std::string>();
|
||||
fs::create_directories(directory);
|
||||
std::string prefix = config["recording"]["screenshot-prefix"].get<std::string>();
|
||||
std::string extension = config["recording"]["screenshot-extension"].get<std::string>();
|
||||
int zfill = config["recording"]["screenshot-zfill"];
|
||||
std::string prefix = configuration("recording", "screenshot prefix").get<std::string>();
|
||||
std::string extension = configuration("recording", "screenshot extension").get<std::string>();
|
||||
int zfill = configuration("recording", "screenshot zfill");
|
||||
fs::path path = sb::get_next_file_name(directory, zfill, prefix, extension);
|
||||
IMG_SavePNG(surface, path.string().c_str());
|
||||
SDL_FreeSurface(surface);
|
||||
|
@ -78,9 +66,9 @@ void Recorder::grab_stash()
|
|||
{
|
||||
if (!is_recording and !writing_recording)
|
||||
{
|
||||
int length = configuration()["recording"]["max-stash-length"];
|
||||
int length = configuration("recording", "max stash length");
|
||||
std::ostringstream message;
|
||||
message << "stashing most recent " << length / 1000.0f << " seconds of video";
|
||||
message << "stashing most recent " << length << " seconds of video";
|
||||
sb::Log::log(message);
|
||||
most_recent_stash = current_stash;
|
||||
current_stash = Stash();
|
||||
|
@ -107,7 +95,7 @@ void Recorder::write_most_recent_frames()
|
|||
most_recent_stash.audio_buffer_lengths.erase(most_recent_stash.audio_buffer_lengths.begin());
|
||||
}
|
||||
audio_file.close();
|
||||
if (configuration()["recording"]["write-mp4"])
|
||||
if (configuration("recording", "write mp4"))
|
||||
{
|
||||
write_mp4();
|
||||
}
|
||||
|
@ -143,11 +131,11 @@ void Recorder::open_audio_file()
|
|||
|
||||
void Recorder::add_frame()
|
||||
{
|
||||
glm::ivec2 size = get_display().window_size();
|
||||
glm::ivec2 size = display.window_size();
|
||||
int bytes = sb::Display::bpp / 8 * size.x * size.y;
|
||||
unsigned char* pixels = new unsigned char[bytes];
|
||||
get_display().screen_pixels(pixels, size.x, size.y);
|
||||
int max_length = configuration()["recording"]["max-stash-length"];
|
||||
display.screen_pixels(pixels, size.x, size.y);
|
||||
int max_length = configuration("recording", "max stash length");
|
||||
float length = frame_length() * current_stash.pixel_buffers.size();
|
||||
if (length > max_length)
|
||||
{
|
||||
|
@ -156,76 +144,63 @@ void Recorder::add_frame()
|
|||
current_stash.flipped.erase(current_stash.flipped.begin());
|
||||
}
|
||||
current_stash.pixel_buffers.push_back(pixels);
|
||||
current_stash.flipped.push_back(get_root()->is_gl_context);
|
||||
current_stash.flipped.push_back(true);
|
||||
if (is_recording)
|
||||
{
|
||||
unsigned char* vid_pixels = new unsigned char[bytes];
|
||||
memcpy(vid_pixels, pixels, bytes);
|
||||
video_stashes.back().pixel_buffers.push_back(vid_pixels);
|
||||
video_stashes.back().flipped.push_back(get_root()->is_gl_context);
|
||||
video_stashes.back().flipped.push_back(true);
|
||||
if (video_stashes.back().pixel_buffers.size() * frame_length() > max_length)
|
||||
{
|
||||
std::function<void(Stash*)> f = std::bind(&Recorder::write_stash_frames, this, std::placeholders::_1);
|
||||
std::thread writing(f, &video_stashes.back());
|
||||
writing.detach();
|
||||
// int frame_offset = video_stashes.back().frame_offset;
|
||||
// std::vector<unsigned char*> pixel_buffers = video_stashes.back().pixel_buffers;
|
||||
// std::vector<bool> flipped = video_stashes.back().flipped;
|
||||
// for (int ii = frame_offset; ii < pixel_buffers.size() + frame_offset; ii++)
|
||||
// {
|
||||
// SDL_Surface* frame = get_display().screen_surface_from_pixels(
|
||||
// pixel_buffers[ii - frame_offset], flipped[ii - frame_offset]);
|
||||
// std::stringstream name;
|
||||
// name << sb::pad(ii, 5) << ".png";
|
||||
// fs::path path = current_video_directory / name.str();
|
||||
// SDL_Log("%s (%i, %i) (%i, %i, %i, %i)", path.c_str(), frame->w, frame->h,
|
||||
// ((unsigned char*) frame->pixels)[0], ((unsigned char*) frame->pixels)[1],
|
||||
// ((unsigned char*) frame->pixels)[2], ((unsigned char*) frame->pixels)[3]);
|
||||
// IMG_SavePNG(frame, path.string().c_str());
|
||||
// }
|
||||
// end_recording();
|
||||
// write_stash_frames(video_stashes.back().pixel_buffers,
|
||||
// video_stashes.back().flipped,
|
||||
// video_stashes.back().frame_offset);
|
||||
video_stashes.push_back(Stash(video_stashes.back().frame_offset +
|
||||
video_stashes.back().pixel_buffers.size()));
|
||||
video_stashes.push_back(Stash(video_stashes.back().frame_offset + video_stashes.back().pixel_buffers.size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int Recorder::get_memory_size()
|
||||
float Recorder::get_memory_size() const
|
||||
{
|
||||
glm::ivec2 window = get_display().window_size();
|
||||
int bytes_per_frame = sb::Display::bpp / 8 * window.x * window.y,
|
||||
size_in_bytes = 0;
|
||||
for (Stash& stash : in_game_stashes)
|
||||
glm::ivec2 window = display.window_size();
|
||||
int bytes_per_frame = sb::Display::bpp / 8 * window.x * window.y;
|
||||
int size_in_bytes = 0;
|
||||
for (const Stash& stash : in_game_stashes)
|
||||
{
|
||||
size_in_bytes += stash.pixel_buffers.size() * bytes_per_frame;
|
||||
for (int& length : stash.audio_buffer_lengths)
|
||||
for (const int& length : stash.audio_buffer_lengths)
|
||||
{
|
||||
size_in_bytes += length;
|
||||
}
|
||||
}
|
||||
for (Stash& stash : video_stashes)
|
||||
for (const Stash& stash : video_stashes)
|
||||
{
|
||||
size_in_bytes += stash.pixel_buffers.size() * bytes_per_frame;
|
||||
}
|
||||
size_in_bytes += current_stash.pixel_buffers.size() * bytes_per_frame;
|
||||
for (int& length : current_stash.audio_buffer_lengths)
|
||||
for (const int& length : current_stash.audio_buffer_lengths)
|
||||
{
|
||||
size_in_bytes += length;
|
||||
}
|
||||
size_in_bytes += most_recent_stash.pixel_buffers.size() * bytes_per_frame;
|
||||
for (int& length : most_recent_stash.audio_buffer_lengths)
|
||||
for (const int& length : most_recent_stash.audio_buffer_lengths)
|
||||
{
|
||||
size_in_bytes += length;
|
||||
}
|
||||
return size_in_bytes / 1000000;
|
||||
return size_in_bytes / 1'000'000.0;
|
||||
}
|
||||
|
||||
void Recorder::log_video_memory_size() const
|
||||
{
|
||||
std::ostringstream message;
|
||||
message << "Video memory size is " << std::fixed << std::setprecision(2) << get_memory_size() << "MB";
|
||||
sb::Log::log(message);
|
||||
}
|
||||
|
||||
void Recorder::make_directory()
|
||||
{
|
||||
fs::path root = configuration()["recording"]["video-directory"].get<std::string>();
|
||||
fs::path root = configuration("recording", "video directory").get<std::string>();
|
||||
fs::create_directories(root);
|
||||
fs::path directory = sb::get_next_file_name(root, 5, "video-");
|
||||
fs::create_directories(directory);
|
||||
|
@ -237,35 +212,29 @@ void Recorder::write_stash_frames(Stash* stash)
|
|||
SDL_Log("Writing stash offset %i to %s...", stash->frame_offset, current_video_directory.string().c_str());
|
||||
SDL_Surface* frame;
|
||||
GifWriter gif_writer;
|
||||
int gif_frame_length = configuration()["recording"]["gif-frame-length"];
|
||||
fs::path gif_path = sb::get_next_file_name(
|
||||
current_video_directory, 3, "gif-", ".gif");
|
||||
float gif_frame_length = configuration("recording", "gif frame length");
|
||||
fs::path gif_path = sb::get_next_file_name(current_video_directory, 3, "gif-", ".gif");
|
||||
float elapsed = 0, last_gif_write = 0, gif_write_overflow = 0;
|
||||
for (int ii = stash->frame_offset; not stash->pixel_buffers.empty(); ii++)
|
||||
for (int ii = stash->frame_offset; !stash->pixel_buffers.empty(); ii++)
|
||||
{
|
||||
frame = get_display().screen_surface_from_pixels(
|
||||
stash->pixel_buffers.front(), stash->flipped.front());
|
||||
frame = display.screen_surface_from_pixels(stash->pixel_buffers.front(), stash->flipped.front());
|
||||
std::stringstream name;
|
||||
name << sb::pad(ii, 5) << ".png";
|
||||
fs::path path = current_video_directory / name.str();
|
||||
IMG_SavePNG(frame, path.string().c_str());
|
||||
if (ii == stash->frame_offset or
|
||||
elapsed - last_gif_write + gif_write_overflow >= gif_frame_length)
|
||||
if (ii == stash->frame_offset || elapsed - last_gif_write + gif_write_overflow >= gif_frame_length)
|
||||
{
|
||||
if (ii == stash->frame_offset)
|
||||
{
|
||||
GifBegin(&gif_writer, gif_path.string().c_str(), frame->w,
|
||||
frame->h, gif_frame_length * 100);
|
||||
GifBegin(&gif_writer, gif_path.string().c_str(), frame->w, frame->h, gif_frame_length * 100);
|
||||
}
|
||||
else
|
||||
{
|
||||
gif_write_overflow += elapsed - (last_gif_write + gif_frame_length);
|
||||
last_gif_write = elapsed;
|
||||
}
|
||||
SDL_Surface* converted = SDL_ConvertSurfaceFormat(
|
||||
frame, SDL_PIXELFORMAT_ABGR8888, 0);
|
||||
GifWriteFrame(&gif_writer, (const uint8_t*) converted->pixels,
|
||||
frame->w, frame->h, gif_frame_length * 100);
|
||||
SDL_Surface* converted = SDL_ConvertSurfaceFormat(frame, SDL_PIXELFORMAT_ABGR8888, 0);
|
||||
GifWriteFrame(&gif_writer, (const uint8_t*) converted->pixels, frame->w, frame->h, gif_frame_length * 100);
|
||||
}
|
||||
elapsed += frame_length();
|
||||
delete[] stash->pixel_buffers.front();
|
||||
|
@ -280,7 +249,7 @@ void Recorder::keep_stash()
|
|||
{
|
||||
in_game_stashes.push_back(current_stash);
|
||||
current_stash = Stash();
|
||||
auto max_stashes = configuration()["recording"]["max-in-game-stashes"];
|
||||
auto max_stashes = configuration("recording", "max in game stashes");
|
||||
if (in_game_stashes.size() > max_stashes)
|
||||
{
|
||||
Stash& stash = in_game_stashes.front();
|
||||
|
@ -322,7 +291,7 @@ void Recorder::finish_writing_video()
|
|||
}
|
||||
}
|
||||
video_stashes.clear();
|
||||
if (configuration()["recording"]["write-mp4"])
|
||||
if (configuration("recording", "write mp4"))
|
||||
{
|
||||
write_mp4();
|
||||
}
|
||||
|
@ -334,13 +303,21 @@ void Recorder::finish_writing_video()
|
|||
* This requires ffmpeg to be installed on the user's system. Might only work on Linux (?) */
|
||||
void Recorder::write_mp4()
|
||||
{
|
||||
glm::ivec2 size = get_display().window_size();
|
||||
glm::ivec2 size = display.window_size();
|
||||
std::ostringstream mp4_command;
|
||||
std::string pixel_format = configuration()["recording"]["mp4-pixel-format"].get<std::string>();
|
||||
std::string pixel_format = configuration("recording", "mp4 pixel format").get<std::string>();
|
||||
int audio_frequency;
|
||||
Mix_QuerySpec(&audio_frequency, nullptr, nullptr);
|
||||
fs::path images_match = current_video_directory / "%05d.png";
|
||||
mp4_command << "ffmpeg -f s16le -ac 2 -ar 22050 -i " << current_audio_path.string() <<
|
||||
int frame_count = 0;
|
||||
for (auto& p: fs::directory_iterator(current_video_directory))
|
||||
{
|
||||
frame_count++;
|
||||
}
|
||||
float video_length = frame_count * frame_length();
|
||||
mp4_command << "ffmpeg -f s16le -ac 2 -ar " << audio_frequency << " -i " << current_audio_path.string() <<
|
||||
" -f image2 -framerate " << (1.0f / frame_length()) <<
|
||||
" -i " << images_match.string() << " -s " << size.x << "x" << size.y <<
|
||||
" -i " << images_match.string() << " -s " << size.x << "x" << size.y << " -t " << video_length <<
|
||||
" -c:v libx264 -crf 17 -pix_fmt " << pixel_format << " " <<
|
||||
current_video_directory.string() << ".mp4";
|
||||
std::string mp4_command_str = mp4_command.str();
|
||||
|
@ -355,7 +332,7 @@ void Recorder::write_audio(Uint8* stream, int len)
|
|||
|
||||
void Recorder::update(float timestamp)
|
||||
{
|
||||
if (is_recording and get_memory_size() > configuration()["recording"]["max-video-memory"])
|
||||
if (is_recording && get_memory_size() > configuration("recording", "max video memory"))
|
||||
{
|
||||
end_recording();
|
||||
}
|
||||
|
@ -366,16 +343,15 @@ void Recorder::update(float timestamp)
|
|||
void Recorder::process_audio(void* context, Uint8* stream, int len)
|
||||
{
|
||||
Recorder* recorder = static_cast<Recorder*>(context);
|
||||
if (recorder->is_active())
|
||||
if (recorder->configuration("recording", "enabled"))
|
||||
{
|
||||
int max_length = recorder->configuration()["recording"]["max-stash-length"];
|
||||
int max_length = recorder->configuration("recording", "max stash length");
|
||||
float length = recorder->frame_length() * recorder->current_stash.pixel_buffers.size();
|
||||
if (length > max_length)
|
||||
{
|
||||
delete[] recorder->current_stash.audio_buffers.front();
|
||||
recorder->current_stash.audio_buffers.erase(recorder->current_stash.audio_buffers.begin());
|
||||
recorder->current_stash.audio_buffer_lengths.erase(
|
||||
recorder->current_stash.audio_buffer_lengths.begin());
|
||||
recorder->current_stash.audio_buffer_lengths.erase(recorder->current_stash.audio_buffer_lengths.begin());
|
||||
}
|
||||
Uint8* stream_copy = new Uint8[len];
|
||||
std::memcpy(stream_copy, stream, len);
|
||||
|
|
124
src/Recorder.hpp
124
src/Recorder.hpp
|
@ -1,11 +1,11 @@
|
|||
/* +------------------------------------------------------+
|
||||
____/ \____ /| - Open source game framework licensed to freely use, |
|
||||
\ / / | copy, modify and sell without restriction |
|
||||
+--\ ^__^ /--+ | |
|
||||
| ~/ \~ | | - created for <https://foam.shampoo.ooo> |
|
||||
| ~~~~~~~~~~~~ | +------------------------------------------------------+
|
||||
| SPACE ~~~~~ | /
|
||||
| ~~~~~~~ BOX |/
|
||||
/* ✨ +------------------------------------------------------+
|
||||
____/ \____ ✨/| Open source game framework licensed to freely use, |
|
||||
✨\ / / | copy, and modify. Created for 🌠dank.game🌠 |
|
||||
+--\ . . /--+ | |
|
||||
| ~/ ︶ \👍| | 🌐 https://open.shampoo.ooo/shampoo/spacebox |
|
||||
| ~~~🌊~~~~🌊~ | +------------------------------------------------------+
|
||||
| SPACE 🪐🅱 OX | /
|
||||
| 🌊 ~ ~~~~ ~~ |/
|
||||
+-------------*/
|
||||
|
||||
#pragma once
|
||||
|
@ -25,59 +25,85 @@
|
|||
#include "glm/ext.hpp"
|
||||
#include "json/json.hpp"
|
||||
#include "filesystem.hpp"
|
||||
#include "Node.hpp"
|
||||
#include "Configuration.hpp"
|
||||
#include "Animation.hpp"
|
||||
#include "Log.hpp"
|
||||
|
||||
struct Stash
|
||||
namespace sb
|
||||
{
|
||||
std::vector<unsigned char*> pixel_buffers;
|
||||
std::vector<bool> flipped;
|
||||
std::vector<Uint8*> audio_buffers;
|
||||
std::vector<int> audio_buffer_lengths;
|
||||
int frame_offset;
|
||||
struct Stash
|
||||
{
|
||||
std::vector<unsigned char*> pixel_buffers;
|
||||
std::vector<bool> flipped;
|
||||
std::vector<Uint8*> audio_buffers;
|
||||
std::vector<int> audio_buffer_lengths;
|
||||
int frame_offset;
|
||||
|
||||
Stash(int frame_offset = 0) : frame_offset(frame_offset) {}
|
||||
};
|
||||
Stash(int frame_offset = 0) : frame_offset(frame_offset) {}
|
||||
};
|
||||
|
||||
class Recorder : public Node
|
||||
{
|
||||
class Recorder
|
||||
{
|
||||
|
||||
private:
|
||||
private:
|
||||
|
||||
Stash current_stash = Stash();
|
||||
Stash most_recent_stash;
|
||||
std::list<Stash> in_game_stashes;
|
||||
std::list<Stash> video_stashes;
|
||||
fs::path current_video_directory, current_audio_path;
|
||||
bool is_recording = false, writing_recording = false, writing_most_recent = false;
|
||||
std::ofstream audio_file;
|
||||
Stash current_stash = Stash();
|
||||
Stash most_recent_stash;
|
||||
std::list<Stash> in_game_stashes;
|
||||
std::list<Stash> video_stashes;
|
||||
fs::path current_video_directory, current_audio_path;
|
||||
bool is_recording = false, writing_recording = false, writing_most_recent = false;
|
||||
std::ofstream audio_file;
|
||||
sb::Configuration& configuration;
|
||||
sb::Display& display;
|
||||
|
||||
float frame_length();
|
||||
static void process_audio(void*, Uint8*, int);
|
||||
float frame_length();
|
||||
static void process_audio(void*, Uint8*, int);
|
||||
|
||||
public:
|
||||
public:
|
||||
|
||||
Animation animation = Animation(std::bind(&Recorder::add_frame, this));
|
||||
Animation animation = Animation(std::bind(&Recorder::add_frame, this));
|
||||
|
||||
Recorder(Node*);
|
||||
void respond(SDL_Event&);
|
||||
void capture_screen();
|
||||
void grab_stash();
|
||||
void write_most_recent_frames();
|
||||
void start_recording();
|
||||
void open_audio_file();
|
||||
void add_frame();
|
||||
int get_memory_size();
|
||||
void make_directory();
|
||||
void write_stash_frames(Stash*);
|
||||
void keep_stash();
|
||||
void end_recording();
|
||||
void finish_writing_video();
|
||||
void write_mp4();
|
||||
void write_audio(Uint8*, int);
|
||||
void update(float timestamp);
|
||||
virtual std::string class_name() const { return "Recorder"; }
|
||||
Recorder(sb::Configuration& configuration, sb::Display& display);
|
||||
|
||||
/*!
|
||||
* If not recording, start recording. If recording, stop recording.
|
||||
*
|
||||
* If a recording is being written, recording will not begin, and a warning will be logged.
|
||||
*/
|
||||
void toggle();
|
||||
|
||||
/*!
|
||||
* Save the current screen pixels as a PNG in the path specified by the configuration.
|
||||
*
|
||||
* Parent directories in the path will be created if necessary. The file name will be auto generated based on the
|
||||
* configuration.
|
||||
*
|
||||
* An index will be included in the file name, one higher than the highest of previous screenshots found in the output
|
||||
* directory.
|
||||
*/
|
||||
void capture_screen();
|
||||
|
||||
void grab_stash();
|
||||
void write_most_recent_frames();
|
||||
void start_recording();
|
||||
void open_audio_file();
|
||||
void add_frame();
|
||||
float get_memory_size() const;
|
||||
|
||||
/*!
|
||||
* Write the size of the video memory in MB to the log.
|
||||
*/
|
||||
void log_video_memory_size() const;
|
||||
|
||||
void make_directory();
|
||||
void write_stash_frames(Stash*);
|
||||
void keep_stash();
|
||||
void end_recording();
|
||||
void finish_writing_video();
|
||||
void write_mp4();
|
||||
void write_audio(Uint8*, int);
|
||||
void update(float timestamp);
|
||||
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
/* +------------------------------------------------------+
|
||||
____/ \____ /| - Open source game framework licensed to freely use, |
|
||||
\ / / | copy, modify and sell without restriction |
|
||||
+--\ ^__^ /--+ | |
|
||||
| ~/ \~ | | - created for <https://foam.shampoo.ooo> |
|
||||
| ~~~~~~~~~~~~ | +------------------------------------------------------+
|
||||
| SPACE ~~~~~ | /
|
||||
| ~~~~~~~ BOX |/
|
||||
/* ✨ +------------------------------------------------------+
|
||||
____/ \____ ✨/| Open source game framework licensed to freely use, |
|
||||
✨\ / / | copy, and modify. Created for 🌠dank.game🌠 |
|
||||
+--\ . . /--+ | |
|
||||
| ~/ ︶ \👍| | 🌐 https://open.shampoo.ooo/shampoo/spacebox |
|
||||
| ~~~🌊~~~~🌊~ | +------------------------------------------------------+
|
||||
| SPACE 🪐🅱 OX | /
|
||||
| 🌊 ~ ~~~~ ~~ |/
|
||||
+-------------*/
|
||||
|
||||
#include "Texture.hpp"
|
||||
|
||||
using namespace sb;
|
||||
|
||||
Texture::Texture() : GLObject(texture_deleter) {}
|
||||
|
@ -35,8 +36,8 @@ void Texture::generate()
|
|||
|
||||
void Texture::generate(glm::vec2 size, GLenum format, std::optional<GLint> filter_value)
|
||||
{
|
||||
/* Only generate a new texture ID and reallocate memory if the current texture ID hasn't been registered by this object as having identically
|
||||
* sized memory with the same format. */
|
||||
/* Only generate a new texture ID and reallocate memory if the current texture ID hasn't been registered by this object as having
|
||||
* identically sized memory with the same format. */
|
||||
if (!generated() || !_size.has_value() || !_format.has_value() || _size != size || _format != format)
|
||||
{
|
||||
generate();
|
||||
|
@ -105,7 +106,8 @@ void Texture::load(fs::path path)
|
|||
std::unique_ptr<SDL_Surface, decltype(&SDL_FreeSurface)> surface(IMG_Load(path.string().c_str()), SDL_FreeSurface);
|
||||
if (surface.get() != nullptr)
|
||||
{
|
||||
std::unique_ptr<SDL_Surface, decltype(&SDL_FreeSurface)> flipped_surface(rotozoomSurfaceXY(surface.get(), 0, 1, -1, 0), SDL_FreeSurface);
|
||||
std::unique_ptr<SDL_Surface, decltype(&SDL_FreeSurface)> flipped_surface(
|
||||
rotozoomSurfaceXY(surface.get(), 0, 1, -1, 0), SDL_FreeSurface);
|
||||
if (flipped_surface.get() != nullptr)
|
||||
{
|
||||
load(flipped_surface.get());
|
||||
|
@ -134,10 +136,11 @@ void Texture::load(fs::path path)
|
|||
|
||||
void Texture::load(SDL_RWops* rw)
|
||||
{
|
||||
/* Load RW object as path as a surface object to access pixel data and flip into OpenGL orientation. Attach a destructor so it will free
|
||||
* itself when it goes out of scope at the end of this function. */
|
||||
/* Load RW object as path as a surface object to access pixel data and flip into OpenGL orientation. Attach a destructor so it will
|
||||
* free itself when it goes out of scope at the end of this function. */
|
||||
std::unique_ptr<SDL_Surface, decltype(&SDL_FreeSurface)> surface(IMG_Load_RW(rw, 0), SDL_FreeSurface);
|
||||
std::unique_ptr<SDL_Surface, decltype(&SDL_FreeSurface)> flipped_surface(rotozoomSurfaceXY(surface.get(), 0, 1, -1, 0), SDL_FreeSurface);
|
||||
std::unique_ptr<SDL_Surface, decltype(&SDL_FreeSurface)> flipped_surface(
|
||||
rotozoomSurfaceXY(surface.get(), 0, 1, -1, 0), SDL_FreeSurface);
|
||||
load(flipped_surface.get());
|
||||
}
|
||||
|
||||
|
@ -146,7 +149,8 @@ void Texture::load(SDL_Surface* surface)
|
|||
std::ostringstream message;
|
||||
if (surface->w > 0 && surface->h > 0)
|
||||
{
|
||||
message << "Loading image from SDL surface (" << surface->w << "×" << surface->h << ", " << SDL_GetPixelFormatName(surface->format->format) << ")";
|
||||
message << "Loading image from SDL surface (" << surface->w << "×" << surface->h << ", " <<
|
||||
SDL_GetPixelFormatName(surface->format->format) << ")";
|
||||
sb::Log::log(message, sb::Log::VERBOSE);
|
||||
load(surface->pixels, {surface->w, surface->h}, GL_RGBA, GL_UNSIGNED_BYTE);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue