#include "core/rendering/screen.hpp" #include #include #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(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(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(); 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(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(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(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(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(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(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(Options::postfx_presets.size())) return Options::postfx_presets[Options::current_postfx_preset].name.c_str(); } else { if (Options::current_crtpi_preset < static_cast(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(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; } }