Files
coffee_crisis_arcade_edition/source/screen.cpp

473 lines
15 KiB
C++

#include "screen.h"
#include <SDL3/SDL_blendmode.h> // Para SDL_BLENDMODE_BLEND
#include <SDL3/SDL_error.h> // Para SDL_GetError
#include <SDL3/SDL_hints.h> // Para SDL_SetHint, SDL_HINT_RENDER_DRIVER
#include <SDL3/SDL_init.h> // Para SDL_Init, SDL_INIT_VIDEO
#include <SDL3/SDL_log.h> // Para SDL_LogCategory, SDL_LogError, SDL_L...
#include <SDL3/SDL_pixels.h> // Para SDL_PixelFormat
#include <SDL3/SDL_surface.h> // Para SDL_ScaleMode
#include <SDL3/SDL_timer.h> // Para SDL_GetTicks
#include <algorithm> // Para min, max
#include <fstream> // Para basic_ifstream, ifstream
#include <iterator> // Para istreambuf_iterator, operator==
#include <memory> // Para __shared_ptr_access, shared_ptr
#include <string> // Para allocator, char_traits, operator+
#include "asset.h" // Para Asset
#include "external/jail_shader.h" // Para init, render
#include "mouse.h" // Para updateCursorVisibility
#include "notifier.h" // Para Notifier
#include "options.h" // Para Options, options, WindowOptions, Vid...
#include "resource.h" // Para Resource
#include "ui/service_menu.h" // Para ServiceMenu
#include "text.h" // Para Text
// Singleton
Screen *Screen::instance_ = nullptr;
// Inicializa la instancia única del singleton
void Screen::init() { Screen::instance_ = new Screen(); }
// Libera la instancia
void Screen::destroy() { delete Screen::instance_; }
// Obtiene la instancia
Screen *Screen::get() { return Screen::instance_; }
// Constructor
Screen::Screen()
: window_(nullptr),
renderer_(nullptr),
game_canvas_(nullptr),
service_menu_(nullptr),
notifier_(nullptr),
src_rect_(SDL_FRect{0, 0, static_cast<float>(param.game.width), static_cast<float>(param.game.height)}),
dst_rect_(SDL_FRect{0, 0, static_cast<float>(param.game.width), static_cast<float>(param.game.height)})
{
// Arranca SDL VIDEO, crea la ventana y el renderizador
initSDLVideo();
// Crea la textura de destino
game_canvas_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.width, param.game.height);
SDL_SetTextureScaleMode(game_canvas_, SDL_SCALEMODE_NEAREST);
// Crea el objeto de texto
createText();
#ifdef DEBUG
debug_info_.text = text_;
setDebugInfoEnabled(true);
#endif
// Inicializa los shaders
SDL_RenderTexture(renderer_, game_canvas_, nullptr, nullptr);
initShaders();
}
// Destructor
Screen::~Screen()
{
SDL_DestroyTexture(game_canvas_);
SDL_DestroyRenderer(renderer_);
SDL_DestroyWindow(window_);
}
// 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()
{
fps_.increment();
// Renderiza todos los overlays y efectos
renderOverlays();
// Renderiza el contenido del game_canvas_
renderScreen();
}
// Vuelca el contenido del renderizador en pantalla exceptuando ciertas partes
void Screen::coreRender()
{
fps_.increment();
#ifdef DEBUG
renderInfo();
#endif
renderScreen();
}
// Renderiza el contenido del game_canvas_
void Screen::renderScreen()
{
SDL_SetRenderTarget(renderer_, nullptr);
clean();
if (Options::video.shaders)
{
shader::render();
}
else
{
SDL_RenderTexture(renderer_, game_canvas_, nullptr, nullptr);
SDL_RenderPresent(renderer_);
}
}
// Establece el modo de video
void Screen::setFullscreenMode()
{
SDL_SetWindowFullscreen(window_, Options::video.fullscreen);
}
// Camibia entre pantalla completa y ventana
void Screen::toggleFullscreen()
{
Options::video.fullscreen = !Options::video.fullscreen;
setFullscreenMode();
}
// Cambia el tamaño de la ventana
void Screen::setWindowZoom(int zoom)
{
Options::window.size = zoom;
adjustWindowSize();
}
// Reduce el tamaño de la ventana
bool Screen::decWindowSize()
{
if (!Options::video.fullscreen)
{
const int PREVIOUS_ZOOM = Options::window.size;
--Options::window.size;
Options::window.size = std::max(Options::window.size, 1);
if (Options::window.size != PREVIOUS_ZOOM)
{
adjustWindowSize();
return true;
}
}
return false;
}
// Aumenta el tamaño de la ventana
bool Screen::incWindowSize()
{
if (!Options::video.fullscreen)
{
const int PREVIOUS_ZOOM = Options::window.size;
++Options::window.size;
Options::window.size = std::min(Options::window.size, Options::window.max_size);
if (Options::window.size != PREVIOUS_ZOOM)
{
adjustWindowSize();
return true;
}
}
return false;
}
// Actualiza la lógica de la clase
void Screen::update()
{
fps_.calculate(SDL_GetTicks());
shake_effect_.update(src_rect_, dst_rect_);
flash_effect_.update();
if (service_menu_)
service_menu_->update();
if (notifier_)
notifier_->update();
Mouse::updateCursorVisibility();
}
// Actualiza los elementos mínimos
void Screen::coreUpdate()
{
fps_.calculate(SDL_GetTicks());
Mouse::updateCursorVisibility();
}
// 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_);
}
}
// 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_RenderTexture(renderer_, game_canvas_, nullptr, nullptr);
// Vuelca textura temporal a game_canvas_
SDL_SetRenderTarget(renderer_, game_canvas_);
SDL_RenderTexture(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);
}
}
#ifdef DEBUG
// Muestra información por pantalla
void Screen::renderInfo()
{
if (debug_info_.show)
{
// Resolution
debug_info_.text->writeDX(TEXT_COLOR | TEXT_STROKE, param.game.width - debug_info_.text->lenght(Options::video.info) - 2, 1, Options::video.info, 1, param.debug.color, 1, param.debug.color.darken(150));
// FPS
const std::string FPS_TEXT = std::to_string(fps_.lastValue) + " FPS";
debug_info_.text->writeDX(TEXT_COLOR | TEXT_STROKE, param.game.width - debug_info_.text->lenght(FPS_TEXT) - 2, 1 + debug_info_.text->getCharacterSize(), FPS_TEXT, 1, param.debug.color, 1, param.debug.color.darken(150));
}
}
#endif
// Carga el contenido del archivo GLSL
void Screen::loadShaders()
{
if (shader_source_.empty())
{
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());
shader_source_ = std::string((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
}
}
// Inicializa los shaders
void Screen::initShaders()
{
if (Options::video.shaders)
{
loadShaders();
shader::init(window_, game_canvas_, shader_source_);
}
}
// Calcula el tamaño de la ventana
void Screen::adjustWindowSize()
{
if (!Options::video.fullscreen)
{
// Establece el nuevo tamaño
const int WIDTH = param.game.width * Options::window.size;
const int HEIGHT = param.game.height * Options::window.size;
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_SetWindowPosition(window_, std::max(NEW_POS_X, WINDOWS_DECORATIONS_), std::max(NEW_POS_Y, 0));
SDL_SetWindowSize(window_, WIDTH, HEIGHT);
}
}
// Renderiza todos los overlays y efectos
void Screen::renderOverlays()
{
// Dibuja efectos y elementos sobre el game_canvas_
renderShake();
renderFlash();
renderAttenuate();
service_menu_->render();
notifier_->render();
#ifdef DEBUG
renderInfo();
#endif
}
// Atenua la pantalla
void Screen::renderAttenuate()
{
if (attenuate_effect_)
{
SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 64);
SDL_RenderFillRect(renderer_, nullptr);
}
}
// Arranca SDL VIDEO y crea la ventana
bool Screen::initSDLVideo()
{
// Inicializar SDL
if (!SDL_Init(SDL_INIT_VIDEO))
{
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"FATAL: Failed to initialize SDL_VIDEO! SDL Error: %s",
SDL_GetError());
return false;
}
// Obtener información de la pantalla
getDisplayInfo();
// Configurar hint para OpenGL
if (!SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl"))
{
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Warning: Failed to set OpenGL hint!");
}
// Crear ventana
window_ = SDL_CreateWindow(
Options::window.caption.c_str(),
param.game.width * Options::window.size,
param.game.height * Options::window.size,
SDL_WINDOW_OPENGL);
if (!window_)
{
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"FATAL: Failed to create window! SDL Error: %s",
SDL_GetError());
SDL_Quit();
return false;
}
// Crear renderer
renderer_ = SDL_CreateRenderer(window_, nullptr);
if (!renderer_)
{
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"FATAL: Failed to create renderer! SDL Error: %s",
SDL_GetError());
SDL_DestroyWindow(window_);
window_ = nullptr;
SDL_Quit();
return false;
}
// Configurar renderer
SDL_SetRenderDrawColor(renderer_, 0x00, 0x00, 0x00, 0xFF);
SDL_SetRenderLogicalPresentation(renderer_, param.game.width, param.game.height, Options::video.integer_scale ? SDL_LOGICAL_PRESENTATION_INTEGER_SCALE : SDL_LOGICAL_PRESENTATION_LETTERBOX);
SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_BLEND);
SDL_SetRenderVSync(renderer_, Options::video.v_sync ? 1 : SDL_RENDERER_VSYNC_DISABLED);
SDL_SetWindowFullscreen(window_, Options::video.fullscreen);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "SDL Video initialized successfully.");
return true;
}
// Obtiene información sobre la pantalla
void Screen::getDisplayInfo()
{
int i, num_displays = 0;
SDL_DisplayID *displays = SDL_GetDisplays(&num_displays);
if (displays)
{
for (i = 0; i < num_displays; ++i)
{
SDL_DisplayID instance_id = displays[i];
const char *name = SDL_GetDisplayName(instance_id);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Display %" SDL_PRIu32 ": %s", instance_id, name ? name : "Unknown");
}
auto DM = SDL_GetCurrentDisplayMode(displays[0]);
// Calcula el máximo factor de zoom que se puede aplicar a la pantalla
Options::window.max_size = std::min(DM->w / param.game.width, DM->h / param.game.height);
Options::window.size = std::min(Options::window.size, Options::window.max_size);
// Muestra información sobre el tamaño de la pantalla y de la ventana de juego
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Current display mode: %dx%d @ %dHz",
static_cast<int>(DM->w), static_cast<int>(DM->h), static_cast<int>(DM->refresh_rate));
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Window resolution: %dx%d x%d",
static_cast<int>(param.game.width), static_cast<int>(param.game.height), Options::window.size);
Options::video.info = std::to_string(static_cast<int>(DM->w)) + "x" +
std::to_string(static_cast<int>(DM->h)) + " @ " +
std::to_string(static_cast<int>(DM->refresh_rate)) + " Hz";
// 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::window.size = std::min(Options::window.size, MAX_ZOOM);
SDL_free(displays);
}
}
// Alterna entre activar y desactivar los shaders
void Screen::toggleShaders()
{
Options::video.shaders = !Options::video.shaders;
initShaders();
}
// Alterna entre activar y desactivar el escalado entero
void Screen::toggleIntegerScale()
{
Options::video.integer_scale = !Options::video.integer_scale;
SDL_SetRenderLogicalPresentation(renderer_, param.game.width, param.game.height, Options::video.integer_scale ? SDL_LOGICAL_PRESENTATION_INTEGER_SCALE : SDL_LOGICAL_PRESENTATION_LETTERBOX);
}
// Alterna entre activar y desactivar el V-Sync
void Screen::toggleVSync()
{
Options::video.v_sync = !Options::video.v_sync;
SDL_SetRenderVSync(renderer_, Options::video.v_sync ? 1 : SDL_RENDERER_VSYNC_DISABLED);
}
// Establece el estado del V-Sync
void Screen::setVSync(bool enabled)
{
Options::video.v_sync = enabled;
SDL_SetRenderVSync(renderer_, enabled ? 1 : SDL_RENDERER_VSYNC_DISABLED);
}
// Obtiene los punteros a los singletones
void Screen::getSingletons()
{
service_menu_ = ServiceMenu::get();
notifier_ = Notifier::get();
}
// Aplica los valores de las opciones
void Screen::applySettings()
{
SDL_SetRenderVSync(renderer_, Options::video.v_sync ? 1 : SDL_RENDERER_VSYNC_DISABLED);
SDL_SetRenderLogicalPresentation(Screen::get()->getRenderer(), param.game.width, param.game.height, Options::video.integer_scale ? SDL_LOGICAL_PRESENTATION_INTEGER_SCALE : SDL_LOGICAL_PRESENTATION_LETTERBOX);
setFullscreenMode();
adjustWindowSize();
}
// Crea el objeto de texto
void Screen::createText()
{
auto texture = std::make_shared<Texture>(getRenderer(), Asset::get()->get("aseprite.png"));
text_ = std::make_shared<Text>(texture, Asset::get()->get("aseprite.txt"));
}