A field is added to the configuration object for user preferences at "storage" > "preferences file", corresponding to a sb::progress::Progress object at sb::Game::preferences. If the file exists, load and merge into the configuration at sb::Game construction time. This allows the project to save user preferences using sb::Game::preferences. Update sb::Game constructor to merge CLI and constructor config overrides into the configuration multiple times during the configuration load phase to make sure the overrides apply to each configuration loaded during the phase. Add an overload to sb::progress::Progress::config for setting a single field of the config section, using the same signature as sb::progress::Progress::set. Added a function to sb::Display for getting the fullscreen status of a sb::Game object's window.
236 lines
8.2 KiB
C++
236 lines
8.2 KiB
C++
/* +-------------------------------------------------------+
|
|
____/ \____ /| Open source game framework licensed to freely use, |
|
|
\ / / | copy, and modify - created for dank.game |
|
|
+--\ ^__^ /--+ | |
|
|
| ~/ \~ | | Download at https://open.shampoo.ooo/shampoo/spacebox |
|
|
| ~~~~~~~~~~~~ | +-------------------------------------------------------+
|
|
| SPACE ~~~~~ | /
|
|
| ~~~~~~~ BOX |/
|
|
+-------------*/
|
|
|
|
#include "Display.hpp"
|
|
|
|
/* Needed to call delegate accessor function */
|
|
#include "Game.hpp"
|
|
|
|
/* Create a Display instance and subscribe to commands */
|
|
sb::Display::Display(Node* parent) : Node(parent)
|
|
{
|
|
delegate().subscribe(&Display::respond, this);
|
|
delegate().subscribe(&Display::respond, this, SDL_WINDOWEVENT);
|
|
}
|
|
|
|
/* Return the (x, y) size in pixels of the window as an integer vector */
|
|
glm::ivec2 sb::Display::window_size() const
|
|
{
|
|
glm::ivec2 size;
|
|
SDL_GetWindowSize(const_cast<SDL_Window*>(window().get()), &size.x, &size.y);
|
|
return size;
|
|
}
|
|
|
|
/* Return the window dimensions in pixels as a Box object. If invert_y is set, the lower left will be (0, 0).
|
|
* Otherwise, the top left will be (0, 0). Setting invert will cause the values to be compatible with
|
|
* glViewport. */
|
|
Box sb::Display::window_box(bool invert_y) const
|
|
{
|
|
return Box(glm::vec2(0, 0), window_size(), invert_y);
|
|
}
|
|
|
|
/* Return a box object with NDC coordinates of the window subsection. The subsection should be specified in
|
|
* pixel coordinates. */
|
|
Box sb::Display::ndc_subsection(const Box& subsection) const
|
|
{
|
|
Box ndc_subsection = ndc;
|
|
Box window = window_box(subsection.inverted_y());
|
|
ndc_subsection.move({
|
|
(subsection.x - window.x) / window.width() * 2.0f,
|
|
(subsection.y - window.y) / window.height() * 2.0f
|
|
});
|
|
ndc_subsection.size({
|
|
subsection.width() / window.width() * 2.0f,
|
|
subsection.height() / window.height() * 2.0f
|
|
});
|
|
return ndc_subsection;
|
|
}
|
|
|
|
/* Convert a NDC area of the screen to a box object containing measurements in pixels that are based on the
|
|
* screen resolution. */
|
|
Box sb::Display::ndc_to_pixel(const Box& ndc) const
|
|
{
|
|
Box pixel_subsection = window_box();
|
|
/* The NDC size is double the ratio of the pixel size */
|
|
pixel_subsection.size(0.5f * ndc.size() * window_box().size(), true);
|
|
/* The NDC center is offset by double the ratio of the size of window in pixels, with the Y-axis inverted */
|
|
pixel_subsection.move(glm::vec2({0.5f, -0.5f}) * ndc.center() * window_box().size());
|
|
return pixel_subsection;
|
|
}
|
|
|
|
/* Get the pixel format of display at specified index (defaults to index 0) */
|
|
Uint32 sb::Display::pixel_format(int display_index) const
|
|
{
|
|
SDL_DisplayMode display_mode;
|
|
if (SDL_GetCurrentDisplayMode(display_index, &display_mode) != 0)
|
|
{
|
|
std::ostringstream message;
|
|
message << "could not get display mode for index " << display_index;
|
|
sb::Log::sdl_error(message.str());
|
|
return SDL_PIXELFORMAT_UNKNOWN;
|
|
}
|
|
else
|
|
{
|
|
return display_mode.format;
|
|
}
|
|
}
|
|
|
|
void sb::Display::screen_pixels(unsigned char* pixels, int w, int h, int x, int y) const
|
|
{
|
|
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__)
|
|
if constexpr (SDL_BYTEORDER == SDL_BIG_ENDIAN)
|
|
{
|
|
format = GL_BGRA;
|
|
}
|
|
else
|
|
{
|
|
#endif
|
|
format = GL_RGBA;
|
|
#if !defined(__EMSCRIPTEN__) && !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
|
|
{
|
|
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);
|
|
delete[] pixels;
|
|
return surface;
|
|
}
|
|
|
|
SDL_Surface* sb::Display::screen_surface_from_pixels(unsigned char* pixels, bool flip) const
|
|
{
|
|
glm::ivec2 size = window_size();
|
|
SDL_Surface* surface;
|
|
Uint32 rmask, gmask, bmask, amask;
|
|
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
|
|
rmask = 0xff000000;
|
|
gmask = 0x00ff0000;
|
|
bmask = 0x0000ff00;
|
|
amask = 0x000000ff;
|
|
#else
|
|
rmask = 0x000000ff;
|
|
gmask = 0x0000ff00;
|
|
bmask = 0x00ff0000;
|
|
amask = 0xff000000;
|
|
#endif
|
|
if (flip)
|
|
{
|
|
SDL_Surface* pixel_surface = SDL_CreateRGBSurfaceFrom(
|
|
pixels, size.x, size.y, bpp, bpp / 8 * size.x, rmask, gmask, bmask, amask);
|
|
surface = zoomSurface(pixel_surface, 1, -1, SMOOTHING_OFF);
|
|
SDL_FreeSurface(pixel_surface);
|
|
}
|
|
else
|
|
{
|
|
surface = SDL_CreateRGBSurface(0, size.x, size.y, bpp, rmask, gmask, bmask, amask);
|
|
std::memcpy(surface->pixels, pixels, bpp / 8 * size.x * size.y);
|
|
}
|
|
return surface;
|
|
}
|
|
|
|
void sb::Display::respond(SDL_Event& event)
|
|
{
|
|
if (get_delegate().compare(event, "fullscreen"))
|
|
{
|
|
toggle_fullscreen();
|
|
}
|
|
else if (event.type == SDL_WINDOWEVENT)
|
|
{
|
|
/* Handle a full window resize event, and only handle intermediate size change events if fluid resize is
|
|
* enabled. */
|
|
if (event.window.event == SDL_WINDOWEVENT_RESIZED ||
|
|
(event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED && configuration()["display"]["fluid resize"]))
|
|
{
|
|
std::ostringstream message;
|
|
message << "Resizing window to " << event.window.data1 << "x" << event.window.data2;
|
|
sb::Log::Level level = event.window.event == SDL_WINDOWEVENT_RESIZED ? sb::Log::INFO : sb::Log::DEBUG;
|
|
sb::Log::log(message, level);
|
|
|
|
/* Set the GL viewport to fill the window (this should probably be optional...) */
|
|
if (SDL_GL_GetCurrentContext() != nullptr)
|
|
{
|
|
glViewport(0, 0, event.window.data1, event.window.data2);
|
|
sb::Log::gl_errors("After glViewport resize");
|
|
}
|
|
|
|
/* Send a general window resize event to the framework */
|
|
Delegate::post("window resize");
|
|
}
|
|
}
|
|
}
|
|
|
|
void sb::Display::toggle_fullscreen() const
|
|
{
|
|
if (configuration()("display", "fullscreen enabled"))
|
|
{
|
|
#if !defined(__EMSCRIPTEN__)
|
|
if (SDL_GetWindowFlags(const_cast<SDL_Window*>(window().get())) & SDL_WINDOW_FULLSCREEN_DESKTOP)
|
|
{
|
|
sb::Log::log("Exit fullscreen requested");
|
|
SDL_SetWindowFullscreen(const_cast<SDL_Window*>(window().get()), 0);
|
|
}
|
|
else
|
|
{
|
|
sb::Log::log("Fullscreen requested");
|
|
SDL_SetWindowFullscreen(const_cast<SDL_Window*>(window().get()), SDL_WINDOW_FULLSCREEN_DESKTOP);
|
|
}
|
|
#else
|
|
EmscriptenFullscreenChangeEvent status;
|
|
emscripten_get_fullscreen_status(&status);
|
|
if (status.isFullscreen)
|
|
{
|
|
sb::Log::log("Exit fullscreen requested");
|
|
emscripten_exit_fullscreen();
|
|
}
|
|
else
|
|
{
|
|
sb::Log::log("Fullscreen requested");
|
|
|
|
/* Set a string to refer to Module.canvas --
|
|
* see https://emscripten.org/docs/api_reference/html5.h.html#registration-functions */
|
|
EM_ASM(specialHTMLTargets["!canvas"] = Module.canvas;);
|
|
emscripten_request_fullscreen("!canvas", true);
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
sb::Log::log("Fullscreen requested, but it is currently disabled by the configuration.", sb::Log::WARN);
|
|
}
|
|
}
|
|
|
|
bool sb::Display::fullscreen_status() const
|
|
{
|
|
#if !defined(__EMSCRIPTEN__)
|
|
return SDL_GetWindowFlags(const_cast<SDL_Window*>(window().get())) & SDL_WINDOW_FULLSCREEN_DESKTOP;
|
|
#else
|
|
EmscriptenFullscreenChangeEvent status;
|
|
emscripten_get_fullscreen_status(&status);
|
|
return status.isFullscreen;
|
|
#endif
|
|
}
|