411 lines
13 KiB
C++
411 lines
13 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 "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, options, WindowOptions, Vid...
|
|
#include "resource.h" // Para Resource
|
|
#include "text.h" // Para Text
|
|
|
|
// [SINGLETON]
|
|
Screen *Screen::screen_ = nullptr;
|
|
|
|
// [SINGLETON] Crearemos el objeto con esta función estática
|
|
void Screen::init() { Screen::screen_ = new Screen(); }
|
|
|
|
// [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()
|
|
: 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
|
|
initSDL();
|
|
|
|
// 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);
|
|
|
|
// Inicializa variables
|
|
adjustRenderLogicalSize();
|
|
|
|
// Muestra la ventana
|
|
show();
|
|
|
|
// Inicializa los shaders
|
|
initShaders(); // Se ha de ejecutar con la ventana visible
|
|
}
|
|
|
|
// 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();
|
|
}
|
|
|
|
// 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(bool mode)
|
|
{
|
|
// Actualiza las opciones
|
|
options.video.fullscreen = mode;
|
|
|
|
// Configura el modo de pantalla
|
|
SDL_SetWindowFullscreen(window_, options.video.fullscreen);
|
|
|
|
initShaders();
|
|
}
|
|
|
|
// 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.zoom = zoom;
|
|
adjustWindowSize();
|
|
}
|
|
|
|
// Reduce el tamaño de la ventana
|
|
bool Screen::decWindowZoom()
|
|
{
|
|
if (!options.video.fullscreen)
|
|
{
|
|
const int PREVIOUS_ZOOM = options.window.zoom;
|
|
--options.window.zoom;
|
|
options.window.zoom = std::max(options.window.zoom, 1);
|
|
|
|
if (options.window.zoom != PREVIOUS_ZOOM)
|
|
{
|
|
adjustWindowSize();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Aumenta el tamaño de la ventana
|
|
bool Screen::incWindowZoom()
|
|
{
|
|
if (!options.video.fullscreen)
|
|
{
|
|
const int PREVIOUS_ZOOM = options.window.zoom;
|
|
++options.window.zoom;
|
|
options.window.zoom = std::min(options.window.zoom, options.window.max_zoom);
|
|
|
|
if (options.window.zoom != 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();
|
|
Notifier::get()->update();
|
|
OnScreenHelp::get()->update();
|
|
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);
|
|
}
|
|
}
|
|
|
|
// Muestra información por pantalla
|
|
void Screen::renderInfo()
|
|
{
|
|
if (show_debug_info_ && Resource::get())
|
|
{
|
|
auto text = Resource::get()->getText("smb2");
|
|
|
|
// FPS
|
|
const std::string FPS_TEXT = std::to_string(fps_.lastValue) + " FPS";
|
|
text->writeColored(param.game.width - text->lenght(FPS_TEXT), 0, FPS_TEXT, ORANGE_SOFT_COLOR);
|
|
|
|
// Resolution
|
|
text->writeColored(0, 0, options.video.info, ORANGE_SOFT_COLOR);
|
|
}
|
|
}
|
|
|
|
// 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());
|
|
shader_source_ = std::string((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
|
|
}
|
|
|
|
// Inicializa los shaders
|
|
void Screen::initShaders()
|
|
{
|
|
if (shader_source_.empty())
|
|
{
|
|
loadShaders();
|
|
}
|
|
shader::init(window_, game_canvas_, shader_source_);
|
|
}
|
|
|
|
// Calcula el tamaño de la ventana
|
|
void Screen::adjustWindowSize()
|
|
{
|
|
// Establece el nuevo tamaño
|
|
if (!options.video.fullscreen)
|
|
{
|
|
const int WIDTH = param.game.width * options.window.zoom;
|
|
const int HEIGHT = param.game.height * options.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_SetRenderViewport(renderer_, &viewport);
|
|
|
|
SDL_SetWindowPosition(window_, std::max(NEW_POS_X, WINDOWS_DECORATIONS_), std::max(NEW_POS_Y, 0));
|
|
SDL_SetWindowSize(window_, WIDTH, HEIGHT);
|
|
|
|
initShaders();
|
|
}
|
|
}
|
|
|
|
// Renderiza todos los overlays y efectos
|
|
void Screen::renderOverlays()
|
|
{
|
|
// Dibuja efectos y elementos sobre el game_canvas_
|
|
renderShake();
|
|
renderFlash();
|
|
renderAttenuate();
|
|
OnScreenHelp::get()->render();
|
|
renderInfo();
|
|
Notifier::get()->render();
|
|
}
|
|
|
|
// 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::initSDL()
|
|
{
|
|
// Indicador de éxito
|
|
auto success = true;
|
|
|
|
// Inicializa SDL
|
|
if (!SDL_Init(SDL_INIT_VIDEO))
|
|
{
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_VIDEO could not initialize! SDL Error: %s", SDL_GetError());
|
|
success = false;
|
|
}
|
|
else
|
|
{
|
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "\n** SDL_VIDEO: INITIALIZING\n");
|
|
|
|
getDisplayInfo();
|
|
|
|
// Establece el filtro de la textura
|
|
/*if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, std::to_string(static_cast<int>(options.video.filter)).c_str()))
|
|
{
|
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Warning: texture filtering not enabled!");
|
|
}*/
|
|
|
|
if (!SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl"))
|
|
{
|
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Warning: opengl not enabled!");
|
|
}
|
|
|
|
// Crea la ventana
|
|
window_ = SDL_CreateWindow(options.window.caption.c_str(), param.game.width * options.window.zoom, param.game.height * options.window.zoom, SDL_WINDOW_OPENGL);
|
|
if (!window_)
|
|
{
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Window could not be created! SDL Error: %s", SDL_GetError());
|
|
success = false;
|
|
}
|
|
else
|
|
{
|
|
// Crea un renderizador para la ventana. El vsync se activa en función de las opciones
|
|
// Uint32 flags = 0;
|
|
if (options.video.v_sync)
|
|
{
|
|
// flags = SDL_RENDERER_PRESENTVSYNC;
|
|
}
|
|
|
|
// La aceleración se activa según el define
|
|
// flags = flags | SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE;
|
|
|
|
renderer_ = SDL_CreateRenderer(window_, nullptr);
|
|
|
|
if (!renderer_)
|
|
{
|
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Renderer could not be created! SDL Error: %s", SDL_GetError());
|
|
success = false;
|
|
}
|
|
else
|
|
{
|
|
SDL_SetRenderDrawColor(renderer_, 0x00, 0x00, 0x00, 0xFF);
|
|
SDL_SetRenderLogicalPresentation(renderer_, param.game.width, param.game.height, SDL_LOGICAL_PRESENTATION_INTEGER_SCALE);
|
|
SDL_SetWindowFullscreen(window_, static_cast<Uint32>(options.video.fullscreen));
|
|
SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_BLEND);
|
|
}
|
|
}
|
|
}
|
|
|
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "** SDL_VIDEO: INITIALIZATION COMPLETE\n");
|
|
return success;
|
|
}
|
|
|
|
// 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_zoom = std::min(DM->w / param.game.width, DM->h / param.game.height);
|
|
options.window.zoom = std::min(options.window.zoom, options.window.max_zoom);
|
|
|
|
// 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.zoom);
|
|
|
|
options.video.info = std::to_string(static_cast<int>(DM->w)) + " X " +
|
|
std::to_string(static_cast<int>(DM->h)) + " AT " +
|
|
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.zoom = std::min(options.window.zoom, MAX_ZOOM);
|
|
|
|
SDL_free(displays);
|
|
}
|
|
} |