#include "jail_shader.h" #include // Para SDL_GL_GetProcAddress, SDL_LogError #include // Para uintptr_t #include // Para strncmp #include // Para runtime_error #include // Para vector #ifdef __APPLE__ #include // Para OpenGL en macOS #include "CoreFoundation/CoreFoundation.h" // Para Core Foundation en macOS #if ESSENTIAL_GL_PRACTICES_SUPPORT_GL3 #include // Para OpenGL 3 en macOS #else // NO ESSENTIAL_GL_PRACTICES_SUPPORT_GL3 #include // Para OpenGL (compatibilidad) en macOS #endif // ESSENTIAL_GL_PRACTICES_SUPPORT_GL3 #else // SI NO ES __APPLE__ #include // Para GLuint, GLint, glTexCoord2f, glVertex2f #endif // __APPLE__ namespace shader { // Constantes const GLuint INVALID_SHADER_ID = 0; const GLuint INVALID_PROGRAM_ID = 0; const GLuint DEFAULT_TEXTURE_ID = 1; // Variables globales SDL_Window *win = nullptr; SDL_Renderer *renderer = nullptr; GLuint programId = 0; SDL_Texture *backBuffer = nullptr; SDL_Point win_size = {320 * 4, 256 * 4}; SDL_FPoint tex_size = {320, 256}; bool usingOpenGL = false; #ifndef __APPLE__ // Declaración de funciones de extensión de OpenGL (evitando GLEW) PFNGLCREATESHADERPROC glCreateShader; PFNGLSHADERSOURCEPROC glShaderSource; PFNGLCOMPILESHADERPROC glCompileShader; PFNGLGETSHADERIVPROC glGetShaderiv; PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog; PFNGLDELETESHADERPROC glDeleteShader; PFNGLATTACHSHADERPROC glAttachShader; PFNGLCREATEPROGRAMPROC glCreateProgram; PFNGLLINKPROGRAMPROC glLinkProgram; PFNGLVALIDATEPROGRAMPROC glValidateProgram; PFNGLGETPROGRAMIVPROC glGetProgramiv; PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog; PFNGLUSEPROGRAMPROC glUseProgram; PFNGLDELETEPROGRAMPROC glDeleteProgram; bool initGLExtensions() { glCreateShader = (PFNGLCREATESHADERPROC)SDL_GL_GetProcAddress("glCreateShader"); glShaderSource = (PFNGLSHADERSOURCEPROC)SDL_GL_GetProcAddress("glShaderSource"); glCompileShader = (PFNGLCOMPILESHADERPROC)SDL_GL_GetProcAddress("glCompileShader"); glGetShaderiv = (PFNGLGETSHADERIVPROC)SDL_GL_GetProcAddress("glGetShaderiv"); glGetShaderInfoLog = (PFNGLGETSHADERINFOLOGPROC)SDL_GL_GetProcAddress("glGetShaderInfoLog"); glDeleteShader = (PFNGLDELETESHADERPROC)SDL_GL_GetProcAddress("glDeleteShader"); glAttachShader = (PFNGLATTACHSHADERPROC)SDL_GL_GetProcAddress("glAttachShader"); glCreateProgram = (PFNGLCREATEPROGRAMPROC)SDL_GL_GetProcAddress("glCreateProgram"); glLinkProgram = (PFNGLLINKPROGRAMPROC)SDL_GL_GetProcAddress("glLinkProgram"); glValidateProgram = (PFNGLVALIDATEPROGRAMPROC)SDL_GL_GetProcAddress("glValidateProgram"); glGetProgramiv = (PFNGLGETPROGRAMIVPROC)SDL_GL_GetProcAddress("glGetProgramiv"); glGetProgramInfoLog = (PFNGLGETPROGRAMINFOLOGPROC)SDL_GL_GetProcAddress("glGetProgramInfoLog"); glUseProgram = (PFNGLUSEPROGRAMPROC)SDL_GL_GetProcAddress("glUseProgram"); glDeleteProgram = (PFNGLDELETEPROGRAMPROC)SDL_GL_GetProcAddress("glDeleteProgram"); return glCreateShader && glShaderSource && glCompileShader && glGetShaderiv && glGetShaderInfoLog && glDeleteShader && glAttachShader && glCreateProgram && glLinkProgram && glValidateProgram && glGetProgramiv && glGetProgramInfoLog && glUseProgram && glDeleteProgram; } #endif // Función para verificar errores de OpenGL void checkGLError(const char *operation) { GLenum error = glGetError(); if (error != GL_NO_ERROR) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error OpenGL en %s: 0x%x", operation, error); } } // Función para compilar un shader a partir de un std::string GLuint compileShader(const std::string &source, GLuint shader_type) { if (source.empty()) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "ERROR FATAL: El código fuente del shader está vacío."); throw std::runtime_error("ERROR FATAL: El código fuente del shader está vacío."); } // Crear identificador del shader GLuint shader_id = glCreateShader(shader_type); if (shader_id == INVALID_SHADER_ID) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error al crear el shader."); checkGLError("glCreateShader"); return INVALID_SHADER_ID; } // Agregar una directiva según el tipo de shader std::string directive = (shader_type == GL_VERTEX_SHADER) ? "#define VERTEX\n" : "#define FRAGMENT\n"; const char *sources[2] = {directive.c_str(), source.c_str()}; // Especificar el código fuente del shader glShaderSource(shader_id, 2, sources, nullptr); checkGLError("glShaderSource"); // Compilar el shader glCompileShader(shader_id); checkGLError("glCompileShader"); // Verificar si la compilación fue exitosa GLint compiled_ok = GL_FALSE; glGetShaderiv(shader_id, GL_COMPILE_STATUS, &compiled_ok); if (compiled_ok != GL_TRUE) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error en la compilación del shader (%d)!", shader_id); GLint log_length; glGetShaderiv(shader_id, GL_INFO_LOG_LENGTH, &log_length); if (log_length > 0) { std::vector log(log_length); glGetShaderInfoLog(shader_id, log_length, &log_length, log.data()); SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Registro de compilación del shader: %s", log.data()); } glDeleteShader(shader_id); return INVALID_SHADER_ID; } return shader_id; } // Función para compilar un programa de shaders (vertex y fragment) a partir de std::string GLuint compileProgram(const std::string &vertex_shader_source, const std::string &fragment_shader_source) { GLuint program_id = glCreateProgram(); if (program_id == INVALID_PROGRAM_ID) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error al crear el programa de shaders."); checkGLError("glCreateProgram"); return INVALID_PROGRAM_ID; } // Si el fragment shader está vacío, reutilizamos el código del vertex shader GLuint vertex_shader_id = compileShader(vertex_shader_source, GL_VERTEX_SHADER); GLuint fragment_shader_id = compileShader(fragment_shader_source.empty() ? vertex_shader_source : fragment_shader_source, GL_FRAGMENT_SHADER); if (vertex_shader_id != INVALID_SHADER_ID && fragment_shader_id != INVALID_SHADER_ID) { // Asociar los shaders al programa glAttachShader(program_id, vertex_shader_id); checkGLError("glAttachShader vertex"); glAttachShader(program_id, fragment_shader_id); checkGLError("glAttachShader fragment"); glLinkProgram(program_id); checkGLError("glLinkProgram"); // Verificar el estado del enlace GLint isLinked = GL_FALSE; glGetProgramiv(program_id, GL_LINK_STATUS, &isLinked); if (isLinked == GL_FALSE) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error al enlazar el programa de shaders."); GLint log_length; glGetProgramiv(program_id, GL_INFO_LOG_LENGTH, &log_length); if (log_length > 0) { std::vector log(log_length); glGetProgramInfoLog(program_id, log_length, &log_length, log.data()); SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Registro de enlace del programa: %s", log.data()); } glDeleteProgram(program_id); program_id = INVALID_PROGRAM_ID; } else { glValidateProgram(program_id); checkGLError("glValidateProgram"); // Log de información del programa (solo si hay información) GLint log_length; glGetProgramiv(program_id, GL_INFO_LOG_LENGTH, &log_length); if (log_length > 1) // > 1 porque algunos drivers devuelven 1 para cadena vacía { std::vector log(log_length); glGetProgramInfoLog(program_id, log_length, &log_length, log.data()); SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Registro de información del programa:\n%s", log.data()); } } } else { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: No se pudieron compilar los shaders."); glDeleteProgram(program_id); program_id = INVALID_PROGRAM_ID; } // Limpiar los shaders (ya no son necesarios después del enlace) if (vertex_shader_id != INVALID_SHADER_ID) { glDeleteShader(vertex_shader_id); } if (fragment_shader_id != INVALID_SHADER_ID) { glDeleteShader(fragment_shader_id); } return program_id; } // Función para obtener el ID de textura OpenGL desde SDL3 GLuint getTextureID(SDL_Texture *texture) { if (!texture) return DEFAULT_TEXTURE_ID; // Intentar obtener el ID de textura OpenGL desde las propiedades de SDL3 SDL_PropertiesID props = SDL_GetTextureProperties(texture); GLuint textureId = 0; // Intentar diferentes nombres de propiedades según la versión de SDL3 textureId = (GLuint)(uintptr_t)SDL_GetPointerProperty(props, "SDL.texture.opengl.texture", nullptr); // Si la primera no funciona, intentar con el nombre alternativo if (textureId == 0) { textureId = (GLuint)(uintptr_t)SDL_GetPointerProperty(props, "texture.opengl.texture", nullptr); } // Si aún no funciona, intentar obtener como número if (textureId == 0) { textureId = (GLuint)SDL_GetNumberProperty(props, "SDL.texture.opengl.texture", DEFAULT_TEXTURE_ID); } // Si ninguna funciona, usar el método manual de bindeo de textura if (textureId == 0) { SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "No se pudo obtener el ID de textura OpenGL, usando ID por defecto (%d)", DEFAULT_TEXTURE_ID); textureId = DEFAULT_TEXTURE_ID; } return textureId; } bool init(SDL_Window *window, SDL_Texture *back_buffer_texture, const std::string &vertex_shader, const std::string &fragment_shader) { shader::win = window; shader::renderer = SDL_GetRenderer(window); shader::backBuffer = back_buffer_texture; if (!shader::renderer) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: No se pudo obtener el renderer de la ventana."); return false; } SDL_GetWindowSize(window, &win_size.x, &win_size.y); SDL_GetTextureSize(back_buffer_texture, &tex_size.x, &tex_size.y); const auto render_name = SDL_GetRendererName(renderer); if (!render_name) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: No se pudo obtener el nombre del renderer."); return false; } // Verificar que el renderer sea OpenGL if (!strncmp(render_name, "opengl", 6)) { #ifndef __APPLE__ if (!initGLExtensions()) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "ERROR: No se han podido inicializar las extensiones de OpenGL."); usingOpenGL = false; return false; } #endif // Compilar el programa de shaders utilizando std::string programId = compileProgram(vertex_shader, fragment_shader); if (programId == INVALID_PROGRAM_ID) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "ERROR: No se pudo compilar el programa de shaders."); usingOpenGL = false; return false; } } else { SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "ADVERTENCIA: El driver del renderer no es OpenGL (%s).", render_name); usingOpenGL = false; return false; } usingOpenGL = true; SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Shader system initialized successfully."); return true; } void render() { // Establece el color de fondo SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); SDL_SetRenderTarget(renderer, nullptr); SDL_RenderClear(renderer); if (usingOpenGL && programId != INVALID_PROGRAM_ID) { // Guardar estados de OpenGL GLint oldProgramId; glGetIntegerv(GL_CURRENT_PROGRAM, &oldProgramId); GLint oldViewport[4]; glGetIntegerv(GL_VIEWPORT, oldViewport); GLboolean wasTextureEnabled = glIsEnabled(GL_TEXTURE_2D); GLint oldTextureId; glGetIntegerv(GL_TEXTURE_BINDING_2D, &oldTextureId); // Obtener y bindear la textura GLuint textureId = getTextureID(backBuffer); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, textureId); checkGLError("glBindTexture"); // Usar nuestro programa de shaders glUseProgram(programId); checkGLError("glUseProgram"); // Recupera el tamaño lógico configurado con SDL_RenderSetLogicalSize int logicalW, logicalH; SDL_RendererLogicalPresentation mode; SDL_GetRenderLogicalPresentation(renderer, &logicalW, &logicalH, &mode); if (logicalW == 0 || logicalH == 0) { logicalW = win_size.x; logicalH = win_size.y; } // Cálculo del viewport int viewportX = 0, viewportY = 0, viewportW = win_size.x, viewportH = win_size.y; const bool USE_INTEGER_SCALE = mode == SDL_LOGICAL_PRESENTATION_INTEGER_SCALE; if (USE_INTEGER_SCALE) { // Calcula el factor de escalado entero máximo que se puede aplicar int scaleX = win_size.x / logicalW; int scaleY = win_size.y / logicalH; int scale = (scaleX < scaleY ? scaleX : scaleY); if (scale < 1) { scale = 1; } viewportW = logicalW * scale; viewportH = logicalH * scale; viewportX = (win_size.x - viewportW) / 2; viewportY = (win_size.y - viewportH) / 2; } else { // Letterboxing: preserva la relación de aspecto usando una escala flotante float windowAspect = static_cast(win_size.x) / win_size.y; float logicalAspect = static_cast(logicalW) / logicalH; if (windowAspect > logicalAspect) { viewportW = static_cast(logicalAspect * win_size.y); viewportX = (win_size.x - viewportW) / 2; } else { viewportH = static_cast(win_size.x / logicalAspect); viewportY = (win_size.y - viewportH) / 2; } } glViewport(viewportX, viewportY, viewportW, viewportH); checkGLError("glViewport"); // Configurar la proyección ortográfica usando el espacio lógico glMatrixMode(GL_PROJECTION); glLoadIdentity(); // Queremos que el origen esté en la esquina superior izquierda del espacio lógico. glOrtho(0, static_cast(logicalW), static_cast(logicalH), 0, -1, 1); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); // Dibuja el quad con las coordenadas ajustadas. // Se asignan las coordenadas de textura "normales" para que no quede espejado horizontalmente, // y se mantiene el flip vertical para que la imagen no aparezca volteada. glBegin(GL_TRIANGLE_STRIP); // Vértice superior izquierdo glTexCoord2f(0.0f, 1.0f); glVertex2f(0.0f, 0.0f); // Vértice superior derecho glTexCoord2f(1.0f, 1.0f); glVertex2f(static_cast(logicalW), 0.0f); // Vértice inferior izquierdo glTexCoord2f(0.0f, 0.0f); glVertex2f(0.0f, static_cast(logicalH)); // Vértice inferior derecho glTexCoord2f(1.0f, 0.0f); glVertex2f(static_cast(logicalW), static_cast(logicalH)); glEnd(); checkGLError("render quad"); SDL_GL_SwapWindow(win); // Restaurar estados de OpenGL glUseProgram(oldProgramId); glBindTexture(GL_TEXTURE_2D, oldTextureId); if (!wasTextureEnabled) { glDisable(GL_TEXTURE_2D); } glViewport(oldViewport[0], oldViewport[1], oldViewport[2], oldViewport[3]); } else { // Fallback a renderizado normal de SDL SDL_RenderTexture(renderer, backBuffer, nullptr, nullptr); SDL_RenderPresent(renderer); } } void cleanup() { if (programId != INVALID_PROGRAM_ID) { glDeleteProgram(programId); programId = INVALID_PROGRAM_ID; SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Programa de shaders liberado."); } // Reinicializar variables win = nullptr; renderer = nullptr; backBuffer = nullptr; usingOpenGL = false; } bool isUsingOpenGL() { return usingOpenGL; } GLuint getProgramId() { return programId; } } // namespace shader