spacebox/src/Display.cpp

173 lines
6.2 KiB
C++

/* /\ +--------------------------------------------------------------+
____/ \____ /| - zlib/MIT/Unlicenced game framework licensed to freely use, |
\ / / | copy, and modify without restriction |
+--\ ^__^ /--+ | |
| ~/ \~ | | - originally created at [http://nugget.fun] |
| ~~~~~~~~~~~~ | +--------------------------------------------------------------+
| SPACE ~~~~~ | /
| ~~~~~~~ BOX |/
+-------------*/
#include "Display.hpp"
/* Create a Display instance and subscribe to commands */
sb::Display::Display(Node* parent) : Node(parent)
{
get_delegate().subscribe(&Display::respond, this);
}
/* 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;
}
}
/* 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;
if constexpr (SDL_BYTEORDER == SDL_BIG_ENDIAN)
{
format = GL_BGRA;
}
else
{
format = GL_RGBA;
}
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);
}
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);
}
}
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);
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;
}
/* Handle fullscreen request */
void sb::Display::respond(SDL_Event& event)
{
if (get_delegate().compare(event, "fullscreen"))
{
toggle_fullscreen();
}
}
/* Use SDL window flags to determine if fullscreen is on or off and use SDL fullscreen
* function to toggle the fullscreen state */
void sb::Display::toggle_fullscreen() const
{
if (SDL_GetWindowFlags(const_cast<SDL_Window*>(window())) & SDL_WINDOW_FULLSCREEN)
{
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);
}
}