Files
aee/source/core/rendering/screen.cpp
2026-04-05 01:03:48 +02:00

380 lines
14 KiB
C++

#include "core/rendering/screen.hpp"
#include <cstdio>
#include <iostream>
#include "core/rendering/overlay.hpp"
#include "core/rendering/sdl3gpu/sdl3gpu_shader.hpp"
#include "game/defines.hpp"
#include "game/options.hpp"
#include "utils/utils.hpp"
Screen* Screen::instance_ = nullptr;
void Screen::init() {
instance_ = new Screen();
}
void Screen::destroy() {
delete instance_;
instance_ = nullptr;
}
auto Screen::get() -> Screen* {
return instance_;
}
Screen::Screen() {
// Carrega opcions guardades
zoom_ = Options::window.zoom;
fullscreen_ = Options::window.fullscreen;
calculateMaxZoom();
if (zoom_ < 1) zoom_ = 1;
if (zoom_ > max_zoom_) zoom_ = max_zoom_;
int w = GAME_WIDTH * zoom_;
int h = Options::video.aspect_ratio_4_3 ? static_cast<int>(GAME_HEIGHT * 1.2F) * zoom_ : GAME_HEIGHT * zoom_;
window_ = SDL_CreateWindow(Texts::WINDOW_TITLE, w, h, fullscreen_ ? SDL_WINDOW_FULLSCREEN : 0);
renderer_ = SDL_CreateRenderer(window_, nullptr);
SDL_SetRenderLogicalPresentation(renderer_, GAME_WIDTH, GAME_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX);
texture_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, GAME_WIDTH, GAME_HEIGHT);
SDL_SetTextureScaleMode(texture_, SDL_SCALEMODE_NEAREST);
// Inicialitza backend GPU si l'acceleració està activada
initShaders();
std::cout << "Screen initialized: " << w << "x" << h << " (zoom " << zoom_ << ", max " << max_zoom_ << ")\n";
}
Screen::~Screen() {
// Guarda opcions abans de destruir
Options::window.zoom = zoom_;
Options::window.fullscreen = fullscreen_;
// Destrueix el backend GPU
if (shader_backend_) {
auto* gpu = dynamic_cast<Rendering::SDL3GPUShader*>(shader_backend_.get());
if (gpu) gpu->destroy();
shader_backend_.reset();
}
if (texture_) SDL_DestroyTexture(texture_);
if (renderer_) SDL_DestroyRenderer(renderer_);
if (window_) SDL_DestroyWindow(window_);
}
void Screen::initShaders() {
if (!Options::video.gpu_acceleration) return;
shader_backend_ = std::make_unique<Rendering::SDL3GPUShader>();
const std::string FALLBACK_DRIVER = "none";
shader_backend_->setPreferredDriver(
Options::video.gpu_acceleration ? "" : FALLBACK_DRIVER);
// init() rep la finestra i la textura (la textura s'usa com a referència, el GPU fa uploadPixels)
if (!shader_backend_->init(window_, texture_, "", "")) {
std::cerr << "GPU shader backend initialization failed, using SDL_Renderer fallback\n";
shader_backend_.reset();
return;
}
gpu_driver_ = shader_backend_->getDriverName();
std::cout << "GPU driver: " << gpu_driver_ << '\n';
// Aplica opcions de vídeo
shader_backend_->setScaleMode(Options::video.integer_scale);
shader_backend_->setStretchFilter(Options::video.stretch_filter_linear);
shader_backend_->setStretch4_3(Options::video.aspect_ratio_4_3);
shader_backend_->setLinearUpscale(Options::video.linear_upscale);
shader_backend_->setDownscaleAlgo(Options::video.downscale_algo);
if (Options::video.supersampling) {
shader_backend_->setOversample(3);
}
// Resol el shader actiu des del config
if (Options::video.current_shader == "crtpi") {
shader_backend_->setActiveShader(Rendering::ShaderType::CRTPI);
} else {
shader_backend_->setActiveShader(Rendering::ShaderType::POSTFX);
}
// Resol presets per nom
for (int i = 0; i < static_cast<int>(Options::postfx_presets.size()); i++) {
if (Options::postfx_presets[i].name == Options::video.current_postfx_preset) {
Options::current_postfx_preset = i;
break;
}
}
for (int i = 0; i < static_cast<int>(Options::crtpi_presets.size()); i++) {
if (Options::crtpi_presets[i].name == Options::video.current_crtpi_preset) {
Options::current_crtpi_preset = i;
break;
}
}
applyCurrentPostFXPreset();
applyCurrentCrtPiPreset();
}
void Screen::present(Uint32* pixel_data) {
fps_.increment();
fps_.calculate(SDL_GetTicks());
updateRenderInfo();
Overlay::render(pixel_data);
if (shader_backend_ && shader_backend_->isHardwareAccelerated() && Options::video.shader_enabled) {
// Path GPU: puja els píxels i renderitza amb shaders
shader_backend_->uploadPixels(pixel_data, GAME_WIDTH, GAME_HEIGHT);
shader_backend_->render();
} else if (shader_backend_ && shader_backend_->isHardwareAccelerated()) {
// GPU activa però shaders desactivats: renderitza net (sense efectes)
Rendering::PostFXParams clean{};
shader_backend_->setPostFXParams(clean);
shader_backend_->uploadPixels(pixel_data, GAME_WIDTH, GAME_HEIGHT);
shader_backend_->render();
} else {
// Fallback SDL_Renderer
SDL_UpdateTexture(texture_, nullptr, pixel_data, GAME_WIDTH * sizeof(Uint32));
SDL_RenderClear(renderer_);
SDL_RenderTexture(renderer_, texture_, nullptr, nullptr);
SDL_RenderPresent(renderer_);
}
}
void Screen::toggleFullscreen() {
fullscreen_ = !fullscreen_;
SDL_SetWindowFullscreen(window_, fullscreen_);
if (!fullscreen_) {
adjustWindowSize();
}
}
void Screen::incZoom() {
if (fullscreen_ || zoom_ >= max_zoom_) return;
zoom_++;
adjustWindowSize();
}
void Screen::decZoom() {
if (fullscreen_ || zoom_ <= 1) return;
zoom_--;
adjustWindowSize();
}
void Screen::setZoom(int zoom) {
if (zoom < 1 || zoom > max_zoom_ || fullscreen_) return;
zoom_ = zoom;
adjustWindowSize();
}
void Screen::toggleShaders() {
Options::video.shader_enabled = !Options::video.shader_enabled;
if (Options::video.shader_enabled) {
applyCurrentPostFXPreset();
}
}
void Screen::toggleSupersampling() {
if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) return;
Options::video.supersampling = !Options::video.supersampling;
shader_backend_->setOversample(Options::video.supersampling ? 3 : 1);
}
void Screen::toggleAspectRatio() {
Options::video.aspect_ratio_4_3 = !Options::video.aspect_ratio_4_3;
if (shader_backend_) {
shader_backend_->setStretch4_3(Options::video.aspect_ratio_4_3);
}
if (!fullscreen_) {
adjustWindowSize();
}
}
void Screen::toggleIntegerScale() {
Options::video.integer_scale = !Options::video.integer_scale;
if (shader_backend_) {
shader_backend_->setScaleMode(Options::video.integer_scale);
}
}
void Screen::toggleStretchFilter() {
Options::video.stretch_filter_linear = !Options::video.stretch_filter_linear;
if (shader_backend_) {
shader_backend_->setStretchFilter(Options::video.stretch_filter_linear);
}
}
void Screen::nextShaderType() {
if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) return;
if (shader_backend_->getActiveShader() == Rendering::ShaderType::POSTFX) {
shader_backend_->setActiveShader(Rendering::ShaderType::CRTPI);
Options::video.current_shader = "crtpi";
applyCurrentCrtPiPreset();
} else {
shader_backend_->setActiveShader(Rendering::ShaderType::POSTFX);
Options::video.current_shader = "postfx";
applyCurrentPostFXPreset();
}
}
void Screen::nextPreset() {
if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) return;
if (shader_backend_->getActiveShader() == Rendering::ShaderType::POSTFX) {
if (Options::postfx_presets.empty()) return;
Options::current_postfx_preset = (Options::current_postfx_preset + 1) % static_cast<int>(Options::postfx_presets.size());
Options::video.current_postfx_preset = Options::postfx_presets[Options::current_postfx_preset].name;
applyCurrentPostFXPreset();
} else {
if (Options::crtpi_presets.empty()) return;
Options::current_crtpi_preset = (Options::current_crtpi_preset + 1) % static_cast<int>(Options::crtpi_presets.size());
Options::video.current_crtpi_preset = Options::crtpi_presets[Options::current_crtpi_preset].name;
applyCurrentCrtPiPreset();
}
}
void Screen::prevShaderType() {
// Només dues opcions — prev == next
nextShaderType();
}
void Screen::prevPreset() {
if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) return;
if (shader_backend_->getActiveShader() == Rendering::ShaderType::POSTFX) {
if (Options::postfx_presets.empty()) return;
int n = static_cast<int>(Options::postfx_presets.size());
Options::current_postfx_preset = (Options::current_postfx_preset - 1 + n) % n;
Options::video.current_postfx_preset = Options::postfx_presets[Options::current_postfx_preset].name;
applyCurrentPostFXPreset();
} else {
if (Options::crtpi_presets.empty()) return;
int n = static_cast<int>(Options::crtpi_presets.size());
Options::current_crtpi_preset = (Options::current_crtpi_preset - 1 + n) % n;
Options::video.current_crtpi_preset = Options::crtpi_presets[Options::current_crtpi_preset].name;
applyCurrentCrtPiPreset();
}
}
auto Screen::getCurrentPresetName() const -> const char* {
if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) return "---";
if (shader_backend_->getActiveShader() == Rendering::ShaderType::POSTFX) {
if (Options::current_postfx_preset < static_cast<int>(Options::postfx_presets.size()))
return Options::postfx_presets[Options::current_postfx_preset].name.c_str();
} else {
if (Options::current_crtpi_preset < static_cast<int>(Options::crtpi_presets.size()))
return Options::crtpi_presets[Options::current_crtpi_preset].name.c_str();
}
return "---";
}
void Screen::setActiveShader(Rendering::ShaderType type) {
if (shader_backend_) {
shader_backend_->setActiveShader(type);
}
}
void Screen::applyCurrentPostFXPreset() {
if (!shader_backend_ || Options::postfx_presets.empty()) return;
const auto& preset = Options::postfx_presets[Options::current_postfx_preset];
Rendering::PostFXParams p;
p.vignette = preset.vignette;
p.scanlines = preset.scanlines;
p.chroma = preset.chroma;
p.mask = preset.mask;
p.gamma = preset.gamma;
p.curvature = preset.curvature;
p.bleeding = preset.bleeding;
p.flicker = preset.flicker;
shader_backend_->setPostFXParams(p);
}
void Screen::applyCurrentCrtPiPreset() {
if (!shader_backend_ || Options::crtpi_presets.empty()) return;
const auto& preset = Options::crtpi_presets[Options::current_crtpi_preset];
Rendering::CrtPiParams p;
p.scanline_weight = preset.scanline_weight;
p.scanline_gap_brightness = preset.scanline_gap_brightness;
p.bloom_factor = preset.bloom_factor;
p.input_gamma = preset.input_gamma;
p.output_gamma = preset.output_gamma;
p.mask_brightness = preset.mask_brightness;
p.curvature_x = preset.curvature_x;
p.curvature_y = preset.curvature_y;
p.mask_type = preset.mask_type;
p.enable_scanlines = preset.enable_scanlines;
p.enable_multisample = preset.enable_multisample;
p.enable_gamma = preset.enable_gamma;
p.enable_curvature = preset.enable_curvature;
p.enable_sharper = preset.enable_sharper;
shader_backend_->setCrtPiParams(p);
}
auto Screen::isHardwareAccelerated() const -> bool {
return shader_backend_ && shader_backend_->isHardwareAccelerated();
}
auto Screen::getActiveShaderName() const -> const char* {
if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) return "SENSE GPU";
return shader_backend_->getActiveShader() == Rendering::ShaderType::POSTFX ? "POSTFX" : "CRT-PI";
}
void Screen::updateRenderInfo() {
static const Uint32 start_ticks = SDL_GetTicks();
std::string driver = gpu_driver_.empty() ? "sdl" : toLower(gpu_driver_);
// Segment 0: FPS + driver (sempre visible)
std::string fps_driver = std::to_string(fps_.last_value) + " fps - " + driver;
// Segment 1: shader + preset (només si shaders actius)
std::string shader_seg;
if (Options::video.shader_enabled) {
shader_seg = " - " + toLower(getActiveShaderName()) + " " + toLower(getCurrentPresetName());
}
// Segment 2: supersampling indicator
const char* ss_seg = (Options::video.shader_enabled && Options::video.supersampling) ? " (ss)" : nullptr;
// Segment 3: hora (només si show_time)
char time_buf[32] = {0};
if (Options::render_info.show_time) {
Uint32 elapsed = SDL_GetTicks() - start_ticks;
int minutes = elapsed / 60000;
int seconds = (elapsed / 1000) % 60;
int centis = (elapsed / 10) % 100;
snprintf(time_buf, sizeof(time_buf), " - %d:%02d.%02d", minutes, seconds, centis);
}
Overlay::setRenderInfoSegments(
fps_driver.c_str(),
shader_seg.empty() ? nullptr : shader_seg.c_str(),
ss_seg,
time_buf[0] ? time_buf : nullptr);
}
void Screen::adjustWindowSize() {
int w = GAME_WIDTH * zoom_;
// Si 4:3 actiu, l'alçada visual és 240 per zoom (200 * 1.2)
int h = Options::video.aspect_ratio_4_3 ? static_cast<int>(GAME_HEIGHT * 1.2F) * zoom_ : GAME_HEIGHT * zoom_;
SDL_SetWindowSize(window_, w, h);
SDL_SetWindowPosition(window_, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
}
void Screen::calculateMaxZoom() {
SDL_DisplayID display = SDL_GetPrimaryDisplay();
const SDL_DisplayMode* mode = SDL_GetCurrentDisplayMode(display);
if (mode) {
int max_w = mode->w / GAME_WIDTH;
int max_h = mode->h / GAME_HEIGHT;
max_zoom_ = (max_w < max_h) ? max_w : max_h;
if (max_zoom_ < 1) max_zoom_ = 1;
}
}