213 lines
7.8 KiB
C++
213 lines
7.8 KiB
C++
/* +------------------------------------------------------+
|
|
____/ \____ /| - Open source game framework licensed to freely use, |
|
|
\ / / | copy, modify and sell without restriction |
|
|
+--\ ^__^ /--+ | |
|
|
| ~/ \~ | | - created for <https://foam.shampoo.ooo> |
|
|
| ~~~~~~~~~~~~ | +------------------------------------------------------+
|
|
| SPACE ~~~~~ | /
|
|
| ~~~~~~~ BOX |/
|
|
+-------------*/
|
|
|
|
#include "Display.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()), &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__) && !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::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())) & SDL_WINDOW_FULLSCREEN_DESKTOP)
|
|
{
|
|
sb::Log::log("Fullscreen requested");
|
|
SDL_SetWindowFullscreen(const_cast<SDL_Window*>(window()), 0);
|
|
}
|
|
else
|
|
{
|
|
sb::Log::log("Exit fullscreen requested");
|
|
SDL_SetWindowFullscreen(const_cast<SDL_Window*>(window()), SDL_WINDOW_FULLSCREEN_DESKTOP);
|
|
}
|
|
#else
|
|
EmscriptenFullscreenChangeEvent status;
|
|
emscripten_get_fullscreen_status(&status);
|
|
if (!status.isFullscreen)
|
|
{
|
|
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);
|
|
}
|
|
else
|
|
{
|
|
sb::Log::log("Exit fullscreen requested");
|
|
emscripten_exit_fullscreen();
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
sb::Log::log("Fullscreen requested, but it is currently disabled by the configuration.", sb::Log::WARN);
|
|
}
|
|
}
|