Files
vibe4_shaders/source/window_manager.cpp
Sergio Valor fb2edd4df5 Arreglar zoom y texturas: filtro NEAREST y guardas robustas
- Añadir SDL_SetTextureScaleMode(render_texture_, SDL_SCALEMODE_NEAREST) para pixel-perfect
- Implementar sistema de fallback robusto para detección de display
- Restaurar funcionalidad completa de zoom con centrado y guardas
- Las texturas ahora se ven pixel-perfect sin blur
- El zoom respeta límites del escritorio correctamente

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-28 08:28:13 +02:00

367 lines
11 KiB
C++

#include "window_manager.h"
#include <SDL3/SDL_init.h> // for SDL_Init
#include <SDL3/SDL_error.h> // for SDL_GetError
#include <SDL3/SDL_video.h> // for SDL_CreateWindow, SDL_GetDisplayBounds
#include <iostream> // for cout
// Incluir backends específicos
// TODO: Reactivar cuando se implemente compilación Objective-C++
// #ifdef __APPLE__
// #include "backends/metal_renderer.h"
// #endif
#if defined(_WIN32) || defined(__linux__)
#include "backends/vulkan_renderer.h"
#endif
// Fallback SDL siempre disponible
#include "backends/sdl_renderer.h"
namespace vibe4 {
WindowManager::WindowManager() = default;
WindowManager::~WindowManager() {
shutdown();
}
bool WindowManager::initialize(const char* title, int width, int height, int zoom) {
logical_width_ = width;
logical_height_ = height;
current_zoom_ = zoom;
// Inicializar SDL
if (!SDL_Init(SDL_INIT_VIDEO)) {
std::cout << "¡SDL no se pudo inicializar! Error: " << SDL_GetError() << std::endl;
return false;
}
// Crear ventana SDL (sin multiplicar por zoom - trabajamos nativo)
if (!createSDLWindow(title, width, height)) {
return false;
}
// Detectar y crear el mejor backend disponible
BackendType backend_type = detectBestBackend();
renderer_ = createRenderer(backend_type);
if (!renderer_) {
std::cout << "¡No se pudo crear ningún backend de renderizado!" << std::endl;
return false;
}
// Inicializar el renderer
if (!renderer_->initialize(window_, width, height)) {
std::cout << "¡No se pudo inicializar el backend " << renderer_->getBackendName() << "!" << std::endl;
return false;
}
// Crear textura de renderizado para postprocesado
auto* sdl_renderer = getSDLRenderer();
if (sdl_renderer) {
render_texture_ = SDL_CreateTexture(sdl_renderer, SDL_PIXELFORMAT_RGBA8888,
SDL_TEXTUREACCESS_TARGET, width, height);
if (!render_texture_) {
std::cout << "¡No se pudo crear la textura de renderizado! Error: " << SDL_GetError() << std::endl;
return false;
}
// Configurar filtro nearest neighbor para píxel perfect
SDL_SetTextureScaleMode(render_texture_, SDL_SCALEMODE_NEAREST);
std::cout << "Textura de renderizado creada: " << width << "x" << height << std::endl;
}
std::cout << "Backend de renderizado inicializado: " << renderer_->getBackendName() << std::endl;
return true;
}
void WindowManager::shutdown() {
if (render_texture_) {
SDL_DestroyTexture(render_texture_);
render_texture_ = nullptr;
}
if (renderer_) {
renderer_->shutdown();
renderer_.reset();
}
if (window_) {
SDL_DestroyWindow(window_);
window_ = nullptr;
}
SDL_Quit();
}
bool WindowManager::createSDLWindow(const char* title, int width, int height) {
Uint32 window_flags = SDL_WINDOW_OPENGL; // Empezamos con OpenGL como base
// Agregar flags específicos dependiendo del backend que vayamos a usar
BackendType backend_type = detectBestBackend();
switch (backend_type) {
case BackendType::METAL:
window_flags = SDL_WINDOW_METAL;
break;
case BackendType::VULKAN:
window_flags = SDL_WINDOW_VULKAN;
break;
case BackendType::SDL:
default:
window_flags = SDL_WINDOW_OPENGL;
break;
}
window_ = SDL_CreateWindow(title, width * current_zoom_, height * current_zoom_, window_flags);
if (!window_) {
std::cout << "¡No se pudo crear la ventana! Error: " << SDL_GetError() << std::endl;
return false;
}
return true;
}
BackendType WindowManager::detectBestBackend() const {
// TODO: Reactivar Metal cuando se implemente compilación Objective-C++
#ifdef __APPLE__
return BackendType::SDL; // Temporalmente usar SDL en macOS
#elif defined(_WIN32) || defined(__linux__)
return BackendType::VULKAN; // Windows/Linux usan Vulkan
#else
return BackendType::SDL; // Fallback para otras plataformas
#endif
}
std::unique_ptr<RendererInterface> WindowManager::createRenderer(BackendType type) {
switch (type) {
// TODO: Reactivar cuando se implemente compilación Objective-C++
// #ifdef __APPLE__
// case BackendType::METAL:
// return std::make_unique<MetalRenderer>();
// #endif
#if defined(_WIN32) || defined(__linux__)
case BackendType::VULKAN:
return std::make_unique<VulkanRenderer>();
#endif
case BackendType::SDL:
default:
return std::make_unique<SDLRenderer>();
}
}
void WindowManager::setTitle(const char* title) {
if (window_) {
SDL_SetWindowTitle(window_, title);
}
}
bool WindowManager::setFullscreen(bool enable) {
if (!window_) return false;
bool result = SDL_SetWindowFullscreen(window_, enable);
if (result) {
fullscreen_enabled_ = enable;
if (enable) {
real_fullscreen_enabled_ = false; // Solo uno puede estar activo
}
}
return result;
}
bool WindowManager::setRealFullscreen(bool enable) {
if (!window_) return false;
bool result = SDL_SetWindowFullscreen(window_, enable);
if (result) {
real_fullscreen_enabled_ = enable;
if (enable) {
fullscreen_enabled_ = false; // Solo uno puede estar activo
}
}
return result;
}
void WindowManager::setZoom(int new_zoom) {
// Validar zoom usando el cálculo dinámico del original
int max_zoom = calculateMaxZoom();
new_zoom = std::max(MIN_ZOOM, std::min(new_zoom, max_zoom));
if (new_zoom == current_zoom_) {
return; // No hay cambio
}
// Obtener posición actual del centro de la ventana (lógica del original)
int current_x, current_y;
SDL_GetWindowPosition(window_, &current_x, &current_y);
int current_center_x = current_x + (logical_width_ * current_zoom_) / 2;
int current_center_y = current_y + (logical_height_ * current_zoom_) / 2;
// Calcular nuevo tamaño
int new_width = logical_width_ * new_zoom;
int new_height = logical_height_ * new_zoom;
// Calcular nueva posición (centrada en el punto actual)
int new_x = current_center_x - new_width / 2;
int new_y = current_center_y - new_height / 2;
// Obtener límites del escritorio para no salirse (con fallback robusto)
SDL_Rect display_bounds;
bool bounds_success = false;
// Intentar primero el display de la ventana, luego el primario
SDL_DisplayID display_id = SDL_GetDisplayForWindow(window_);
if (display_id != 0) {
bounds_success = (SDL_GetDisplayBounds(display_id, &display_bounds) == 0);
}
if (!bounds_success) {
// Fallback al display primario como en el original
bounds_success = (SDL_GetDisplayBounds(SDL_GetPrimaryDisplay(), &display_bounds) == 0);
}
if (!bounds_success) {
// Último fallback: valores seguros para resolución común
display_bounds.w = 1920;
display_bounds.h = 1080;
bounds_success = true;
}
if (bounds_success) {
// Aplicar márgenes
int min_x = DESKTOP_MARGIN;
int min_y = DESKTOP_MARGIN;
int max_x = display_bounds.w - new_width - DESKTOP_MARGIN;
int max_y = display_bounds.h - new_height - DESKTOP_MARGIN - DECORATION_HEIGHT;
// Limitar posición
new_x = std::max(min_x, std::min(new_x, max_x));
new_y = std::max(min_y, std::min(new_y, max_y));
}
// Aplicar cambios
SDL_SetWindowSize(window_, new_width, new_height);
SDL_SetWindowPosition(window_, new_x, new_y);
current_zoom_ = new_zoom;
// Notificar al renderer del cambio (mantener resolución lógica)
if (renderer_) {
renderer_->resize(logical_width_, logical_height_);
}
}
void WindowManager::updateWindowSize() {
// Simplificar: usar setZoom que ya maneja todo el centrado y guardas
setZoom(current_zoom_);
}
int WindowManager::calculateMaxZoom() const {
// Obtener información del display usando el método del commit original
int num_displays = 0;
SDL_DisplayID *displays = SDL_GetDisplays(&num_displays);
if (displays == nullptr || num_displays == 0) {
return MIN_ZOOM; // Fallback si no se puede obtener
}
// Obtener el modo de display actual
const auto *dm = SDL_GetCurrentDisplayMode(displays[0]);
if (dm == nullptr) {
SDL_free(displays);
return MIN_ZOOM;
}
// Calcular zoom máximo usando la fórmula de Coffee Crisis del original
const int MAX_ZOOM_CALC = std::min(dm->w / logical_width_, (dm->h - DECORATION_HEIGHT) / logical_height_);
SDL_free(displays);
// Aplicar límites
return std::max(MIN_ZOOM, std::min(MAX_ZOOM_CALC, MAX_ZOOM));
}
void WindowManager::zoomIn() {
int max_zoom = calculateMaxZoom();
if (current_zoom_ < max_zoom) {
setZoom(current_zoom_ + 1);
}
}
void WindowManager::zoomOut() {
if (current_zoom_ > MIN_ZOOM) {
setZoom(current_zoom_ - 1);
}
}
void WindowManager::getSize(int& width, int& height) const {
if (window_) {
SDL_GetWindowSize(window_, &width, &height);
} else {
width = height = 0;
}
}
void WindowManager::getLogicalSize(int& width, int& height) const {
width = logical_width_;
height = logical_height_;
}
BackendType WindowManager::getBackendType() const {
return renderer_ ? renderer_->getBackendType() : BackendType::SDL;
}
const char* WindowManager::getBackendName() const {
return renderer_ ? renderer_->getBackendName() : "None";
}
SDL_Renderer* WindowManager::getSDLRenderer() const {
// Solo funciona si el backend activo es SDL
if (renderer_ && renderer_->getBackendType() == BackendType::SDL) {
auto* sdl_renderer = static_cast<SDLRenderer*>(renderer_.get());
return sdl_renderer->getSDLRenderer();
}
return nullptr;
}
bool WindowManager::setRenderTarget() {
auto* sdl_renderer = getSDLRenderer();
if (!sdl_renderer || !render_texture_) {
return false;
}
// Cambiar el render target a nuestra textura
if (!SDL_SetRenderTarget(sdl_renderer, render_texture_)) {
std::cout << "¡No se pudo establecer render target! Error: " << SDL_GetError() << std::endl;
return false;
}
// Limpiar la textura de renderizado
SDL_SetRenderDrawColor(sdl_renderer, 0, 0, 0, 255);
SDL_RenderClear(sdl_renderer);
return true;
}
void WindowManager::presentFrame() {
auto* sdl_renderer = getSDLRenderer();
if (!sdl_renderer || !render_texture_) {
return;
}
// Volver al render target por defecto (la ventana)
SDL_SetRenderTarget(sdl_renderer, nullptr);
// Limpiar la pantalla
SDL_SetRenderDrawColor(sdl_renderer, 0, 0, 0, 255);
SDL_RenderClear(sdl_renderer);
// Copiar la textura de renderizado a la pantalla 1:1 (sin zoom)
SDL_RenderTexture(sdl_renderer, render_texture_, nullptr, nullptr);
// Presentar el frame final
SDL_RenderPresent(sdl_renderer);
}
} // namespace vibe4