#include "screen.h" #include // Para SDL_PIXELFORMAT_RGBA8888 #include // Para SDL_GetTicks #include // Para max, min #include // Para basic_ifstream, ifstream #include // Para istreambuf_iterator, operator== #include // Para allocator, operator+, char_traits, to_s... #include // Para vector #include "asset.h" // Para Asset #include "dbgtxt.h" // Para dbg_print #include "global_inputs.h" // Para service_pressed_counter #include "jail_shader.h" // Para init, render #include "mouse.h" // Para updateCursorVisibility #include "notifier.h" // Para Notifier #include "on_screen_help.h" // Para OnScreenHelp #include "options.h" // Para Options, OptionsVideo, options, Options... // [SINGLETON] Screen *Screen::screen_ = nullptr; // [SINGLETON] Crearemos el objeto con esta función estática void Screen::init(SDL_Window *window, SDL_Renderer *renderer) { Screen::screen_ = new Screen(window, renderer); } // [SINGLETON] Destruiremos el objeto con esta función estática void Screen::destroy() { delete Screen::screen_; } // [SINGLETON] Con este método obtenemos el objeto y podemos trabajar con él Screen *Screen::get() { return Screen::screen_; } // Constructor Screen::Screen(SDL_Window *window, SDL_Renderer *renderer) : window_(window), renderer_(renderer), game_canvas_(SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.width, param.game.height)), src_rect_({0, 0, param.game.width, param.game.height}), dst_rect_({0, 0, param.game.width, param.game.height}) { // Inicializa variables SDL_DisplayMode DM; SDL_GetCurrentDisplayMode(0, &DM); info_resolution_ = std::to_string(DM.w) + " X " + std::to_string(DM.h) + " AT " + std::to_string(DM.refresh_rate) + " HZ"; adjustRenderLogicalSize(); // Inicializa los shaders initShaders(); // Muestra la ventana show(); } // Destructor Screen::~Screen() { SDL_DestroyTexture(game_canvas_); } // Limpia la pantalla void Screen::clean(Color color) { SDL_SetRenderDrawColor(renderer_, color.r, color.g, color.b, 0xFF); SDL_RenderClear(renderer_); } // Prepara para empezar a dibujar en la textura de juego void Screen::start() { SDL_SetRenderTarget(renderer_, game_canvas_); } // Vuelca el contenido del renderizador en pantalla void Screen::render() { // Renderiza todos los overlays y efectos renderOverlays(); // Renderiza el contenido del game_canvas_ renderScreen(); } // Renderiza el contenido del game_canvas_ void Screen::renderScreen() { SDL_SetRenderTarget(renderer_, nullptr); clean(); if (options.video.shaders) { shader::render(); } else { SDL_RenderCopy(renderer_, game_canvas_, nullptr, nullptr); SDL_RenderPresent(renderer_); } } // Establece el modo de video void Screen::setVideoMode(ScreenVideoMode video_mode) { // Actualiza las opciones options.video.mode = video_mode; // Configura el modo de pantalla Uint32 flags = SDL_GetWindowFlags(window_); if (flags != static_cast(options.video.mode)) { SDL_SetWindowFullscreen(window_, static_cast(options.video.mode)); } initShaders(); } // Camibia entre pantalla completa y ventana void Screen::toggleVideoMode() { options.video.mode = options.video.mode == ScreenVideoMode::WINDOW ? ScreenVideoMode::FULLSCREEN : ScreenVideoMode::WINDOW; setVideoMode(); } // Cambia el tamaño de la ventana void Screen::setWindowZoom(int zoom) { options.video.window.zoom = zoom; adjustWindowSize(); } // Reduce el tamaño de la ventana bool Screen::decWindowZoom() { if (options.video.mode == ScreenVideoMode::WINDOW) { const int PREVIOUS_ZOOM = options.video.window.zoom; --options.video.window.zoom; options.video.window.zoom = std::max(options.video.window.zoom, 1); if (options.video.window.zoom != PREVIOUS_ZOOM) { adjustWindowSize(); return true; } } return false; } // Aumenta el tamaño de la ventana bool Screen::incWindowZoom() { if (options.video.mode == ScreenVideoMode::WINDOW) { const int PREVIOUS_ZOOM = options.video.window.zoom; ++options.video.window.zoom; options.video.window.zoom = std::min(options.video.window.zoom, options.video.window.max_zoom); if (options.video.window.zoom != PREVIOUS_ZOOM) { adjustWindowSize(); return true; } } return false; } // Cambia el tipo de mezcla void Screen::setBlendMode(SDL_BlendMode blendMode) { SDL_SetRenderDrawBlendMode(renderer_, blendMode); } // Actualiza la lógica de la clase void Screen::update() { updateShakeEffect(); updateFlash(); Notifier::get()->update(); updateFPS(); OnScreenHelp::get()->update(); Mouse::updateCursorVisibility(); } // Agita la pantalla void Screen::shake() { // Si no hay un shake effect activo, se guarda una copia de los valores actuales antes de modificarlos if (!shake_effect_.enabled) { shake_effect_.enabled = true; shake_effect_.originalPos = src_rect_.x; shake_effect_.originalWidth = src_rect_.w; src_rect_.w -= shake_effect_.desp; dst_rect_.w = src_rect_.w; } // Si ya hay un shake effect en marcha no se pilla el origen, solo se renuevan los contadores shake_effect_.remaining = shake_effect_.lenght; shake_effect_.counter = shake_effect_.delay; } // Actualiza la logica para agitar la pantalla void Screen::updateShakeEffect() { if (shake_effect_.enabled) { if (shake_effect_.counter > 0) { shake_effect_.counter--; } else { shake_effect_.counter = shake_effect_.delay; const auto srcdesp = shake_effect_.remaining % 2 == 0 ? 0 : shake_effect_.desp; const auto dstdesp = shake_effect_.remaining % 2 == 1 ? 0 : shake_effect_.desp; src_rect_.x = shake_effect_.originalPos + srcdesp; dst_rect_.x = shake_effect_.originalPos + dstdesp; shake_effect_.remaining--; shake_effect_.enabled = shake_effect_.remaining == -1 ? false : true; if (!shake_effect_.enabled) { src_rect_.x = shake_effect_.originalPos; src_rect_.w = shake_effect_.originalWidth; dst_rect_ = src_rect_; } } } } // Pone la pantalla de color void Screen::flash(Color color, int lenght, int delay) { flash_effect_ = FlashEffect(true, lenght, delay, color); } // Actualiza y dibuja el efecto de flash en la pantalla void Screen::renderFlash() { if (flash_effect_.isRendarable()) { SDL_SetRenderDrawColor(renderer_, flash_effect_.color.r, flash_effect_.color.g, flash_effect_.color.b, 0xFF); SDL_RenderClear(renderer_); } } // Actualiza el efecto de flash void Screen::updateFlash() { flash_effect_.update(); } // Atenua la pantalla void Screen::renderAttenuate() { if (attenuate_effect_) { SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 64); SDL_RenderFillRect(renderer_, nullptr); } } // Aplica el efecto de agitar la pantalla void Screen::renderShake() { if (shake_effect_.enabled) { // Guarda el renderizador actual para dejarlo despues como estaba auto current_target = SDL_GetRenderTarget(renderer_); // Crea una textura temporal auto temp_texture = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.width, param.game.height); // Vuelca game_canvas_ a la textura temporal SDL_SetRenderTarget(renderer_, temp_texture); SDL_RenderCopy(renderer_, game_canvas_, nullptr, nullptr); // Vuelca textura temporal a game_canvas_ SDL_SetRenderTarget(renderer_, game_canvas_); SDL_RenderCopy(renderer_, temp_texture, &src_rect_, &dst_rect_); // Elimina la textura temporal SDL_DestroyTexture(temp_texture); // Restaura el renderizador de destino original SDL_SetRenderTarget(renderer_, current_target); } } // Activa / desactiva los shaders void Screen::toggleShaders() { options.video.shaders = !options.video.shaders; } // Activa / desactiva la información de debug void Screen::toggleDebugInfo() { show_debug_info_ = !show_debug_info_; } // Atenua la pantalla void Screen::attenuate(bool value) { attenuate_effect_ = value; } // Calcula los frames por segundo void Screen::updateFPS() { if (SDL_GetTicks() - fps_ticks_ > 1000) { fps_ticks_ = SDL_GetTicks(); fps_ = fps_counter_; fps_counter_ = 0; } } // Muestra información por pantalla void Screen::renderInfo() { if (show_debug_info_) { // FPS const std::string fpstext = std::to_string(fps_) + " FPS"; dbg_print(param.game.width - fpstext.length() * 8, 0, fpstext.c_str(), 255, 255, 0); // Resolution dbg_print(0, 0, info_resolution_.c_str(), 255, 255, 0); // Contador de service_pressed_counter if (const int counter = globalInputs::service_pressed_counter; counter > 0) { dbg_print(0, 8, std::to_string(counter).c_str(), 255, 0, 255); } } } // Carga el contenido del archivo GLSL void Screen::loadShaders() { const std::string GLSL_FILE = param.game.game_area.rect.h == 256 ? "crtpi_256.glsl" : "crtpi_240.glsl"; std::ifstream f(Asset::get()->get(GLSL_FILE).c_str()); shaderSource = std::string((std::istreambuf_iterator(f)), std::istreambuf_iterator()); } // Inicializa los shaders void Screen::initShaders() { if (shaderSource.empty()) { loadShaders(); } shader::init(window_, game_canvas_, shaderSource.c_str()); } // Calcula el tamaño de la ventana void Screen::adjustWindowSize() { options.video.window.max_zoom = getMaxZoom(); // Establece el nuevo tamaño if (options.video.mode == ScreenVideoMode::WINDOW) { const int WIDTH = param.game.width * options.video.window.zoom; const int HEIGHT = param.game.height * options.video.window.zoom; int old_width, old_height; SDL_GetWindowSize(window_, &old_width, &old_height); int old_pos_x, old_pos_y; SDL_GetWindowPosition(window_, &old_pos_x, &old_pos_y); const int NEW_POS_X = old_pos_x + (old_width - WIDTH) / 2; const int NEW_POS_Y = old_pos_y + (old_height - HEIGHT) / 2; SDL_Rect viewport = {0, 0, WIDTH, HEIGHT}; SDL_RenderSetViewport(renderer_, &viewport); SDL_SetWindowPosition(window_, std::max(NEW_POS_X, WINDOWS_DECORATIONS_), std::max(NEW_POS_Y, 0)); SDL_SetWindowSize(window_, WIDTH, HEIGHT); initShaders(); } } // Ajusta el tamaño lógico del renderizador void Screen::adjustRenderLogicalSize() { SDL_RenderSetLogicalSize(renderer_, param.game.width, param.game.height); } // Obtiene el tamaño máximo de zoom posible para la ventana int Screen::getMaxZoom() { // Obtiene información sobre la pantalla SDL_DisplayMode DM; SDL_GetCurrentDisplayMode(0, &DM); // Calcula el máximo factor de zoom que se puede aplicar a la pantalla const int MAX_ZOOM = std::min(DM.w / param.game.width, (DM.h - WINDOWS_DECORATIONS_) / param.game.height); // Normaliza los valores de zoom options.video.window.zoom = std::min(options.video.window.zoom, MAX_ZOOM); return MAX_ZOOM; } // Renderiza todos los overlays y efectos void Screen::renderOverlays() { // Actualiza el contador de FPS ++fps_counter_; // Dibuja efectos y elementos sobre el game_canvas_ renderShake(); renderFlash(); renderAttenuate(); OnScreenHelp::get()->render(); renderInfo(); Notifier::get()->render(); }