#include "core/rendering/screen.hpp" #include #include "core/rendering/overlay.hpp" #include "core/rendering/sdl3gpu/sdl3gpu_shader.hpp" #include "game/defines.hpp" #include "game/options.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; } auto* gpu = dynamic_cast(shader_backend_.get()); if (gpu) { std::cout << "GPU driver: " << gpu->getDriverName() << '\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); } // Aplica presets per defecte (de moment hardcoded, futur: YAML) applyCurrentPostFXPreset(); applyCurrentCrtPiPreset(); } void Screen::present(Uint32* pixel_data) { 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::nextShaderPreset() { if (!shader_backend_ || !shader_backend_->isHardwareAccelerated()) return; // Cicla entre PostFX i CrtPi if (shader_backend_->getActiveShader() == Rendering::ShaderType::POSTFX) { shader_backend_->setActiveShader(Rendering::ShaderType::CRTPI); applyCurrentCrtPiPreset(); } else { shader_backend_->setActiveShader(Rendering::ShaderType::POSTFX); applyCurrentPostFXPreset(); } } void Screen::setActiveShader(Rendering::ShaderType type) { if (shader_backend_) { shader_backend_->setActiveShader(type); } } void Screen::applyCurrentPostFXPreset() { if (!shader_backend_) return; // Preset per defecte "CRT" — futur: carregar des de YAML Rendering::PostFXParams p; p.vignette = 0.4F; p.scanlines = 0.5F; p.chroma = 0.1F; p.mask = 0.0F; p.gamma = 0.0F; p.curvature = 0.0F; p.bleeding = 0.0F; p.flicker = 0.0F; shader_backend_->setPostFXParams(p); } void Screen::applyCurrentCrtPiPreset() { if (!shader_backend_) return; // Preset per defecte — futur: carregar des de YAML Rendering::CrtPiParams p; 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::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; } }