/* +------------------------------------------------------+ ____/ \____ /| - Open source game framework licensed to freely use, | \ / / | copy, modify and sell without restriction | +--\ ^__^ /--+ | | | ~/ \~ | | - created for | | ~~~~~~~~~~~~ | +------------------------------------------------------+ | 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(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(window())) & SDL_WINDOW_FULLSCREEN_DESKTOP) { sb::Log::log("Fullscreen requested"); SDL_SetWindowFullscreen(const_cast(window()), 0); } else { sb::Log::log("Exit fullscreen requested"); SDL_SetWindowFullscreen(const_cast(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); } }