redistribuida la carpeta source

This commit is contained in:
2025-10-26 13:02:45 +01:00
parent 9676e5bc2f
commit 8f49e442de
164 changed files with 303 additions and 30479 deletions

View File

@@ -0,0 +1,316 @@
#include "core/rendering/gif.hpp"
#include <iostream> // Para std::cout
#include <cstring> // Para memcpy, size_t
#include <stdexcept> // Para runtime_error
#include <string> // Para allocator, char_traits, operator==, basic_string
namespace GIF
{
// Función inline para reemplazar el macro READ.
// Actualiza el puntero 'buffer' tras copiar 'size' bytes a 'dst'.
inline void readBytes(const uint8_t *&buffer, void *dst, size_t size)
{
std::memcpy(dst, buffer, size);
buffer += size;
}
void Gif::decompress(int code_length, const uint8_t *input, int input_length, uint8_t *out)
{
// Verifica que el code_length tenga un rango razonable.
if (code_length < 2 || code_length > 12)
{
throw std::runtime_error("Invalid LZW code length");
}
int i, bit;
int prev = -1;
std::vector<DictionaryEntry> dictionary;
int dictionary_ind;
unsigned int mask = 0x01;
int reset_code_length = code_length;
int clear_code = 1 << code_length;
int stop_code = clear_code + 1;
int match_len = 0;
// Inicializamos el diccionario con el tamaño correspondiente.
dictionary.resize(1 << (code_length + 1));
for (dictionary_ind = 0; dictionary_ind < (1 << code_length); dictionary_ind++)
{
dictionary[dictionary_ind].byte = static_cast<uint8_t>(dictionary_ind);
dictionary[dictionary_ind].prev = -1;
dictionary[dictionary_ind].len = 1;
}
dictionary_ind += 2; // Reservamos espacio para clear y stop codes
// Bucle principal: procesar el stream comprimido.
while (input_length > 0)
{
int code = 0;
// Lee (code_length + 1) bits para formar el código.
for (i = 0; i < (code_length + 1); i++)
{
if (input_length <= 0)
{
throw std::runtime_error("Unexpected end of input in decompress");
}
bit = ((*input & mask) != 0) ? 1 : 0;
mask <<= 1;
if (mask == 0x100)
{
mask = 0x01;
input++;
input_length--;
}
code |= (bit << i);
}
if (code == clear_code)
{
// Reinicia el diccionario.
code_length = reset_code_length;
dictionary.resize(1 << (code_length + 1));
for (dictionary_ind = 0; dictionary_ind < (1 << code_length); dictionary_ind++)
{
dictionary[dictionary_ind].byte = static_cast<uint8_t>(dictionary_ind);
dictionary[dictionary_ind].prev = -1;
dictionary[dictionary_ind].len = 1;
}
dictionary_ind += 2;
prev = -1;
continue;
}
else if (code == stop_code)
{
break;
}
if (prev > -1 && code_length < 12)
{
if (code > dictionary_ind)
{
std::cerr << "code = " << std::hex << code
<< ", but dictionary_ind = " << dictionary_ind << std::endl;
throw std::runtime_error("LZW error: code exceeds dictionary_ind.");
}
int ptr;
if (code == dictionary_ind)
{
ptr = prev;
while (dictionary[ptr].prev != -1)
ptr = dictionary[ptr].prev;
dictionary[dictionary_ind].byte = dictionary[ptr].byte;
}
else
{
ptr = code;
while (dictionary[ptr].prev != -1)
ptr = dictionary[ptr].prev;
dictionary[dictionary_ind].byte = dictionary[ptr].byte;
}
dictionary[dictionary_ind].prev = prev;
dictionary[dictionary_ind].len = dictionary[prev].len + 1;
dictionary_ind++;
if ((dictionary_ind == (1 << (code_length + 1))) && (code_length < 11))
{
code_length++;
dictionary.resize(1 << (code_length + 1));
}
}
prev = code;
// Verifica que 'code' sea un índice válido antes de usarlo.
if (code < 0 || static_cast<size_t>(code) >= dictionary.size())
{
std::cerr << "Invalid LZW code " << code
<< ", dictionary size " << dictionary.size() << std::endl;
throw std::runtime_error("LZW error: invalid code encountered");
}
int curCode = code; // Variable temporal para recorrer la cadena.
match_len = dictionary[curCode].len;
while (curCode != -1)
{
// Se asume que dictionary[curCode].len > 0.
out[dictionary[curCode].len - 1] = dictionary[curCode].byte;
if (dictionary[curCode].prev == curCode)
{
std::cerr << "Internal error; self-reference detected." << std::endl;
throw std::runtime_error("Internal error in decompress: self-reference");
}
curCode = dictionary[curCode].prev;
}
out += match_len;
}
}
std::vector<uint8_t> Gif::readSubBlocks(const uint8_t *&buffer)
{
std::vector<uint8_t> data;
uint8_t block_size = *buffer;
buffer++;
while (block_size != 0)
{
data.insert(data.end(), buffer, buffer + block_size);
buffer += block_size;
block_size = *buffer;
buffer++;
}
return data;
}
std::vector<uint8_t> Gif::processImageDescriptor(const uint8_t *&buffer, const std::vector<RGB> &gct, int resolution_bits)
{
ImageDescriptor image_descriptor;
// Lee 9 bytes para el image descriptor.
readBytes(buffer, &image_descriptor, sizeof(ImageDescriptor));
uint8_t lzw_code_size;
readBytes(buffer, &lzw_code_size, sizeof(uint8_t));
std::vector<uint8_t> compressed_data = readSubBlocks(buffer);
int uncompressed_data_length = image_descriptor.image_width * image_descriptor.image_height;
std::vector<uint8_t> uncompressed_data(uncompressed_data_length);
decompress(lzw_code_size, compressed_data.data(), static_cast<int>(compressed_data.size()), uncompressed_data.data());
return uncompressed_data;
}
std::vector<uint32_t> Gif::loadPalette(const uint8_t *buffer)
{
uint8_t header[6];
std::memcpy(header, buffer, 6);
buffer += 6;
ScreenDescriptor screen_descriptor;
std::memcpy(&screen_descriptor, buffer, sizeof(ScreenDescriptor));
buffer += sizeof(ScreenDescriptor);
std::vector<uint32_t> global_color_table;
if (screen_descriptor.fields & 0x80)
{
int global_color_table_size = 1 << (((screen_descriptor.fields & 0x07) + 1));
global_color_table.resize(global_color_table_size);
for (int i = 0; i < global_color_table_size; ++i)
{
uint8_t r = buffer[0];
uint8_t g = buffer[1];
uint8_t b = buffer[2];
global_color_table[i] = (r << 16) | (g << 8) | b;
buffer += 3;
}
}
return global_color_table;
}
std::vector<uint8_t> Gif::processGifStream(const uint8_t *buffer, uint16_t &w, uint16_t &h)
{
// Leer la cabecera de 6 bytes ("GIF87a" o "GIF89a")
uint8_t header[6];
std::memcpy(header, buffer, 6);
buffer += 6;
// Opcional: Validar header
std::string headerStr(reinterpret_cast<char *>(header), 6);
if (headerStr != "GIF87a" && headerStr != "GIF89a")
{
throw std::runtime_error("Formato de archivo GIF inválido.");
}
// Leer el Screen Descriptor (7 bytes, empaquetado sin padding)
ScreenDescriptor screen_descriptor;
readBytes(buffer, &screen_descriptor, sizeof(ScreenDescriptor));
// Asigna ancho y alto
w = screen_descriptor.width;
h = screen_descriptor.height;
int color_resolution_bits = ((screen_descriptor.fields & 0x70) >> 4) + 1;
std::vector<RGB> global_color_table;
if (screen_descriptor.fields & 0x80)
{
int global_color_table_size = 1 << (((screen_descriptor.fields & 0x07) + 1));
global_color_table.resize(global_color_table_size);
std::memcpy(global_color_table.data(), buffer, 3 * global_color_table_size);
buffer += 3 * global_color_table_size;
}
// Supongamos que 'buffer' es el puntero actual y TRAILER es 0x3B
uint8_t block_type = *buffer++;
while (block_type != TRAILER)
{
if (block_type == EXTENSION_INTRODUCER) // 0x21
{
// Se lee la etiqueta de extensión, la cual indica el tipo de extensión.
uint8_t extension_label = *buffer++;
switch (extension_label)
{
case GRAPHIC_CONTROL: // 0xF9
{
// Procesar Graphic Control Extension:
uint8_t blockSize = *buffer++; // Normalmente, blockSize == 4
buffer += blockSize; // Saltamos los 4 bytes del bloque fijo
// Saltar los sub-bloques
uint8_t subBlockSize = *buffer++;
while (subBlockSize != 0)
{
buffer += subBlockSize;
subBlockSize = *buffer++;
}
break;
}
case APPLICATION_EXTENSION: // 0xFF
case COMMENT_EXTENSION: // 0xFE
case PLAINTEXT_EXTENSION: // 0x01
{
// Para estas extensiones, saltamos el bloque fijo y los sub-bloques.
uint8_t blockSize = *buffer++;
buffer += blockSize;
uint8_t subBlockSize = *buffer++;
while (subBlockSize != 0)
{
buffer += subBlockSize;
subBlockSize = *buffer++;
}
break;
}
default:
{
// Si la etiqueta de extensión es desconocida, saltarla también:
uint8_t blockSize = *buffer++;
buffer += blockSize;
uint8_t subBlockSize = *buffer++;
while (subBlockSize != 0)
{
buffer += subBlockSize;
subBlockSize = *buffer++;
}
break;
}
}
}
else if (block_type == IMAGE_DESCRIPTOR)
{
// Procesar el Image Descriptor y retornar los datos de imagen
return processImageDescriptor(buffer, global_color_table, color_resolution_bits);
}
else
{
std::cerr << "Unrecognized block type " << std::hex << static_cast<int>(block_type) << std::endl;
return std::vector<uint8_t>{};
}
block_type = *buffer++;
}
return std::vector<uint8_t>{};
}
std::vector<uint8_t> Gif::loadGif(const uint8_t *buffer, uint16_t &w, uint16_t &h)
{
return processGifStream(buffer, w, h);
}
} // namespace GIF

View File

@@ -0,0 +1,102 @@
#pragma once
#include <cstdint> // Para uint8_t, uint16_t, uint32_t
#include <vector> // Para vector
namespace GIF
{
// Constantes definidas con constexpr, en lugar de macros
constexpr uint8_t EXTENSION_INTRODUCER = 0x21;
constexpr uint8_t IMAGE_DESCRIPTOR = 0x2C;
constexpr uint8_t TRAILER = 0x3B;
constexpr uint8_t GRAPHIC_CONTROL = 0xF9;
constexpr uint8_t APPLICATION_EXTENSION = 0xFF;
constexpr uint8_t COMMENT_EXTENSION = 0xFE;
constexpr uint8_t PLAINTEXT_EXTENSION = 0x01;
#pragma pack(push, 1)
struct ScreenDescriptor
{
uint16_t width;
uint16_t height;
uint8_t fields;
uint8_t background_color_index;
uint8_t pixel_aspect_ratio;
};
struct RGB
{
uint8_t r, g, b;
};
struct ImageDescriptor
{
uint16_t image_left_position;
uint16_t image_top_position;
uint16_t image_width;
uint16_t image_height;
uint8_t fields;
};
#pragma pack(pop)
struct DictionaryEntry
{
uint8_t byte;
int prev;
int len;
};
struct Extension
{
uint8_t extension_code;
uint8_t block_size;
};
struct GraphicControlExtension
{
uint8_t fields;
uint16_t delay_time;
uint8_t transparent_color_index;
};
struct ApplicationExtension
{
uint8_t application_id[8];
uint8_t version[3];
};
struct PlaintextExtension
{
uint16_t left, top, width, height;
uint8_t cell_width, cell_height;
uint8_t foreground_color, background_color;
};
class Gif
{
public:
// Descompone (uncompress) el bloque comprimido usando LZW.
// Este método puede lanzar std::runtime_error en caso de error.
void decompress(int code_length, const uint8_t *input, int input_length, uint8_t *out);
// Carga la paleta (global color table) a partir de un buffer,
// retornándola en un vector de uint32_t (cada color se compone de R, G, B).
std::vector<uint32_t> loadPalette(const uint8_t *buffer);
// Carga el stream GIF; devuelve un vector con los datos de imagen sin comprimir y
// asigna el ancho y alto mediante referencias.
std::vector<uint8_t> loadGif(const uint8_t *buffer, uint16_t &w, uint16_t &h);
private:
// Lee los sub-bloques de datos y los acumula en un std::vector<uint8_t>.
std::vector<uint8_t> readSubBlocks(const uint8_t *&buffer);
// Procesa el Image Descriptor y retorna el vector de datos sin comprimir.
std::vector<uint8_t> processImageDescriptor(const uint8_t *&buffer, const std::vector<RGB> &gct, int resolution_bits);
// Procesa el stream completo del GIF y devuelve los datos sin comprimir.
std::vector<uint8_t> processGifStream(const uint8_t *buffer, uint16_t &w, uint16_t &h);
};
} // namespace GIF

View File

@@ -0,0 +1,462 @@
#include "core/rendering/opengl/opengl_shader.hpp"
#include <SDL3/SDL.h>
#include <cstring>
#include <stdexcept>
#include <vector>
namespace Rendering {
OpenGLShader::~OpenGLShader() {
cleanup();
}
#ifndef __APPLE__
bool OpenGLShader::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");
glGetUniformLocation = (PFNGLGETUNIFORMLOCATIONPROC)SDL_GL_GetProcAddress("glGetUniformLocation");
glUniform2f = (PFNGLUNIFORM2FPROC)SDL_GL_GetProcAddress("glUniform2f");
glGenVertexArrays = (PFNGLGENVERTEXARRAYSPROC)SDL_GL_GetProcAddress("glGenVertexArrays");
glBindVertexArray = (PFNGLBINDVERTEXARRAYPROC)SDL_GL_GetProcAddress("glBindVertexArray");
glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSPROC)SDL_GL_GetProcAddress("glDeleteVertexArrays");
glGenBuffers = (PFNGLGENBUFFERSPROC)SDL_GL_GetProcAddress("glGenBuffers");
glBindBuffer = (PFNGLBINDBUFFERPROC)SDL_GL_GetProcAddress("glBindBuffer");
glBufferData = (PFNGLBUFFERDATAPROC)SDL_GL_GetProcAddress("glBufferData");
glDeleteBuffers = (PFNGLDELETEBUFFERSPROC)SDL_GL_GetProcAddress("glDeleteBuffers");
glVertexAttribPointer = (PFNGLVERTEXATTRIBPOINTERPROC)SDL_GL_GetProcAddress("glVertexAttribPointer");
glEnableVertexAttribArray = (PFNGLENABLEVERTEXATTRIBARRAYPROC)SDL_GL_GetProcAddress("glEnableVertexAttribArray");
return glCreateShader && glShaderSource && glCompileShader && glGetShaderiv &&
glGetShaderInfoLog && glDeleteShader && glAttachShader && glCreateProgram &&
glLinkProgram && glValidateProgram && glGetProgramiv && glGetProgramInfoLog &&
glUseProgram && glDeleteProgram && glGetUniformLocation && glUniform2f &&
glGenVertexArrays && glBindVertexArray && glDeleteVertexArrays &&
glGenBuffers && glBindBuffer && glBufferData && glDeleteBuffers &&
glVertexAttribPointer && glEnableVertexAttribArray;
}
#endif
void OpenGLShader::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);
}
}
GLuint OpenGLShader::compileShader(const std::string& source, GLenum shader_type) {
if (source.empty()) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"ERROR: El código fuente del shader está vacío");
return 0;
}
GLuint shader_id = glCreateShader(shader_type);
if (shader_id == 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error al crear shader");
checkGLError("glCreateShader");
return 0;
}
const char* sources[1] = {source.c_str()};
glShaderSource(shader_id, 1, sources, nullptr);
checkGLError("glShaderSource");
glCompileShader(shader_id);
checkGLError("glCompileShader");
// Verificar compilación
GLint compiled = GL_FALSE;
glGetShaderiv(shader_id, GL_COMPILE_STATUS, &compiled);
if (compiled != GL_TRUE) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Error en compilación del shader");
GLint log_length;
glGetShaderiv(shader_id, GL_INFO_LOG_LENGTH, &log_length);
if (log_length > 0) {
std::vector<char> log(log_length);
glGetShaderInfoLog(shader_id, log_length, &log_length, log.data());
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Log de compilación: %s", log.data());
}
glDeleteShader(shader_id);
return 0;
}
return shader_id;
}
GLuint OpenGLShader::linkProgram(GLuint vertex_shader, GLuint fragment_shader) {
GLuint program = glCreateProgram();
if (program == 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Error al crear programa de shaders");
return 0;
}
glAttachShader(program, vertex_shader);
checkGLError("glAttachShader(vertex)");
glAttachShader(program, fragment_shader);
checkGLError("glAttachShader(fragment)");
glLinkProgram(program);
checkGLError("glLinkProgram");
// Verificar enlace
GLint linked = GL_FALSE;
glGetProgramiv(program, GL_LINK_STATUS, &linked);
if (linked != GL_TRUE) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Error al enlazar programa");
GLint log_length;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_length);
if (log_length > 0) {
std::vector<char> log(log_length);
glGetProgramInfoLog(program, log_length, &log_length, log.data());
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Log de enlace: %s", log.data());
}
glDeleteProgram(program);
return 0;
}
glValidateProgram(program);
checkGLError("glValidateProgram");
return program;
}
void OpenGLShader::createQuadGeometry() {
// Datos del quad: posición (x, y) + coordenadas de textura (u, v)
// Formato: x, y, u, v
float vertices[] = {
// Posición // TexCoords
-1.0f, -1.0f, 0.0f, 0.0f, // Inferior izquierda
1.0f, -1.0f, 1.0f, 0.0f, // Inferior derecha
1.0f, 1.0f, 1.0f, 1.0f, // Superior derecha
-1.0f, 1.0f, 0.0f, 1.0f // Superior izquierda
};
// Índices para dibujar el quad con dos triángulos
unsigned int indices[] = {
0, 1, 2, // Primer triángulo
2, 3, 0 // Segundo triángulo
};
// Generar y configurar VAO
glGenVertexArrays(1, &vao_);
glBindVertexArray(vao_);
checkGLError("glBindVertexArray");
// Generar y configurar VBO
glGenBuffers(1, &vbo_);
glBindBuffer(GL_ARRAY_BUFFER, vbo_);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
checkGLError("glBufferData(VBO)");
// Generar y configurar EBO
glGenBuffers(1, &ebo_);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo_);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
checkGLError("glBufferData(EBO)");
// Atributo 0: Posición (2 floats)
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
checkGLError("glVertexAttribPointer(position)");
// Atributo 1: Coordenadas de textura (2 floats)
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));
glEnableVertexAttribArray(1);
checkGLError("glVertexAttribPointer(texcoord)");
// Desvincular
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
GLuint OpenGLShader::getTextureID(SDL_Texture* texture) {
if (!texture) return 1;
SDL_PropertiesID props = SDL_GetTextureProperties(texture);
GLuint texture_id = 0;
// Intentar obtener ID de textura OpenGL
texture_id = (GLuint)(uintptr_t)SDL_GetPointerProperty(props, "SDL.texture.opengl.texture", nullptr);
if (texture_id == 0) {
texture_id = (GLuint)(uintptr_t)SDL_GetPointerProperty(props, "texture.opengl.texture", nullptr);
}
if (texture_id == 0) {
texture_id = (GLuint)SDL_GetNumberProperty(props, "SDL.texture.opengl.texture", 1);
}
if (texture_id == 0) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"No se pudo obtener ID de textura OpenGL, usando 1 por defecto");
texture_id = 1;
}
return texture_id;
}
bool OpenGLShader::init(SDL_Window* window,
SDL_Texture* texture,
const std::string& vertex_source,
const std::string& fragment_source) {
window_ = window;
back_buffer_ = texture;
renderer_ = SDL_GetRenderer(window);
if (!renderer_) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Error: No se pudo obtener el renderer");
return false;
}
// Obtener tamaños
SDL_GetWindowSize(window_, &window_width_, &window_height_);
SDL_GetTextureSize(back_buffer_, &texture_width_, &texture_height_);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Inicializando shaders: ventana=%dx%d, textura=%.0fx%.0f",
window_width_, window_height_, texture_width_, texture_height_);
// Verificar que es OpenGL
const char* renderer_name = SDL_GetRendererName(renderer_);
if (!renderer_name || strncmp(renderer_name, "opengl", 6) != 0) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Renderer no es OpenGL: %s", renderer_name ? renderer_name : "unknown");
return false;
}
#ifndef __APPLE__
// Inicializar extensiones OpenGL en Windows/Linux
if (!initGLExtensions()) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Error al inicializar extensiones OpenGL");
return false;
}
#endif
// Limpiar shader anterior si existe
if (program_id_ != 0) {
glDeleteProgram(program_id_);
program_id_ = 0;
}
// Compilar shaders
GLuint vertex_shader = compileShader(vertex_source, GL_VERTEX_SHADER);
GLuint fragment_shader = compileShader(fragment_source, GL_FRAGMENT_SHADER);
if (vertex_shader == 0 || fragment_shader == 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Error al compilar shaders");
if (vertex_shader != 0) glDeleteShader(vertex_shader);
if (fragment_shader != 0) glDeleteShader(fragment_shader);
return false;
}
// Enlazar programa
program_id_ = linkProgram(vertex_shader, fragment_shader);
// Limpiar shaders (ya no necesarios tras el enlace)
glDeleteShader(vertex_shader);
glDeleteShader(fragment_shader);
if (program_id_ == 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Error al crear programa de shaders");
return false;
}
// Crear geometría del quad
createQuadGeometry();
// Obtener ubicación del uniform TextureSize
glUseProgram(program_id_);
texture_size_location_ = glGetUniformLocation(program_id_, "TextureSize");
if (texture_size_location_ != -1) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Configurando TextureSize uniform: %.0fx%.0f",
texture_width_, texture_height_);
glUniform2f(texture_size_location_, texture_width_, texture_height_);
checkGLError("glUniform2f(TextureSize)");
} else {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Uniform 'TextureSize' no encontrado en shader");
}
glUseProgram(0);
is_initialized_ = true;
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"** OpenGL 3.3 Shader Backend inicializado correctamente");
return true;
}
void OpenGLShader::render() {
if (!is_initialized_ || program_id_ == 0) {
// Fallback: renderizado SDL normal
SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 255);
SDL_SetRenderTarget(renderer_, nullptr);
SDL_RenderClear(renderer_);
SDL_RenderTexture(renderer_, back_buffer_, nullptr, nullptr);
SDL_RenderPresent(renderer_);
return;
}
// Obtener tamaño actual de ventana (puede haber cambiado)
int current_width, current_height;
SDL_GetWindowSize(window_, &current_width, &current_height);
// Guardar estados OpenGL
GLint old_program;
glGetIntegerv(GL_CURRENT_PROGRAM, &old_program);
GLint old_viewport[4];
glGetIntegerv(GL_VIEWPORT, old_viewport);
GLboolean was_texture_enabled = glIsEnabled(GL_TEXTURE_2D);
GLint old_texture;
glGetIntegerv(GL_TEXTURE_BINDING_2D, &old_texture);
GLint old_vao;
glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &old_vao);
// Preparar renderizado
SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 255);
SDL_SetRenderTarget(renderer_, nullptr);
SDL_RenderClear(renderer_);
// Obtener y bindear textura
GLuint texture_id = getTextureID(back_buffer_);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, texture_id);
checkGLError("glBindTexture");
// Usar nuestro programa
glUseProgram(program_id_);
checkGLError("glUseProgram");
// Configurar viewport (obtener tamaño lógico de SDL)
int logical_w, logical_h;
SDL_RendererLogicalPresentation mode;
SDL_GetRenderLogicalPresentation(renderer_, &logical_w, &logical_h, &mode);
if (logical_w == 0 || logical_h == 0) {
logical_w = current_width;
logical_h = current_height;
}
// Calcular viewport considerando aspect ratio
int viewport_x = 0, viewport_y = 0;
int viewport_w = current_width, viewport_h = current_height;
if (mode == SDL_LOGICAL_PRESENTATION_INTEGER_SCALE) {
int scale_x = current_width / logical_w;
int scale_y = current_height / logical_h;
int scale = (scale_x < scale_y) ? scale_x : scale_y;
if (scale < 1) scale = 1;
viewport_w = logical_w * scale;
viewport_h = logical_h * scale;
viewport_x = (current_width - viewport_w) / 2;
viewport_y = (current_height - viewport_h) / 2;
} else {
float window_aspect = static_cast<float>(current_width) / current_height;
float logical_aspect = static_cast<float>(logical_w) / logical_h;
if (window_aspect > logical_aspect) {
viewport_w = static_cast<int>(logical_aspect * current_height);
viewport_x = (current_width - viewport_w) / 2;
} else {
viewport_h = static_cast<int>(current_width / logical_aspect);
viewport_y = (current_height - viewport_h) / 2;
}
}
glViewport(viewport_x, viewport_y, viewport_w, viewport_h);
checkGLError("glViewport");
// Dibujar quad usando VAO
glBindVertexArray(vao_);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
checkGLError("glDrawElements");
// Presentar
SDL_GL_SwapWindow(window_);
// Restaurar estados OpenGL
glUseProgram(old_program);
glBindTexture(GL_TEXTURE_2D, old_texture);
if (!was_texture_enabled) {
glDisable(GL_TEXTURE_2D);
}
glBindVertexArray(old_vao);
glViewport(old_viewport[0], old_viewport[1], old_viewport[2], old_viewport[3]);
}
void OpenGLShader::setTextureSize(float width, float height) {
if (!is_initialized_ || program_id_ == 0) {
return;
}
texture_width_ = width;
texture_height_ = height;
GLint old_program;
glGetIntegerv(GL_CURRENT_PROGRAM, &old_program);
glUseProgram(program_id_);
if (texture_size_location_ != -1) {
glUniform2f(texture_size_location_, width, height);
checkGLError("glUniform2f(TextureSize)");
}
glUseProgram(old_program);
}
void OpenGLShader::cleanup() {
if (vao_ != 0) {
glDeleteVertexArrays(1, &vao_);
vao_ = 0;
}
if (vbo_ != 0) {
glDeleteBuffers(1, &vbo_);
vbo_ = 0;
}
if (ebo_ != 0) {
glDeleteBuffers(1, &ebo_);
ebo_ = 0;
}
if (program_id_ != 0) {
glDeleteProgram(program_id_);
program_id_ = 0;
}
is_initialized_ = false;
window_ = nullptr;
renderer_ = nullptr;
back_buffer_ = nullptr;
}
} // namespace Rendering

View File

@@ -0,0 +1,98 @@
#pragma once
#include "core/rendering/shader_backend.hpp"
#ifdef __APPLE__
#include <OpenGL/gl3.h>
#else
#include <SDL3/SDL_opengl.h>
#endif
namespace Rendering {
/**
* @brief Backend de shaders usando OpenGL 3.3 Core Profile
*
* Implementa el renderizado de shaders usando APIs modernas de OpenGL:
* - VAO (Vertex Array Objects)
* - VBO (Vertex Buffer Objects)
* - Shaders GLSL #version 330 core
*/
class OpenGLShader : public ShaderBackend {
public:
OpenGLShader() = default;
~OpenGLShader() override;
bool init(SDL_Window* window,
SDL_Texture* texture,
const std::string& vertex_source,
const std::string& fragment_source) override;
void render() override;
void setTextureSize(float width, float height) override;
void cleanup() override;
bool isHardwareAccelerated() const override { return is_initialized_; }
private:
// Funciones auxiliares
bool initGLExtensions();
GLuint compileShader(const std::string& source, GLenum shader_type);
GLuint linkProgram(GLuint vertex_shader, GLuint fragment_shader);
void createQuadGeometry();
GLuint getTextureID(SDL_Texture* texture);
void checkGLError(const char* operation);
// Estado SDL
SDL_Window* window_ = nullptr;
SDL_Renderer* renderer_ = nullptr;
SDL_Texture* back_buffer_ = nullptr;
// Estado OpenGL
GLuint program_id_ = 0;
GLuint vao_ = 0; // Vertex Array Object
GLuint vbo_ = 0; // Vertex Buffer Object
GLuint ebo_ = 0; // Element Buffer Object
// Ubicaciones de uniforms
GLint texture_size_location_ = -1;
// Tamaños
int window_width_ = 0;
int window_height_ = 0;
float texture_width_ = 0.0f;
float texture_height_ = 0.0f;
// Estado
bool is_initialized_ = false;
#ifndef __APPLE__
// Punteros a funciones OpenGL en Windows/Linux
PFNGLCREATESHADERPROC glCreateShader = nullptr;
PFNGLSHADERSOURCEPROC glShaderSource = nullptr;
PFNGLCOMPILESHADERPROC glCompileShader = nullptr;
PFNGLGETSHADERIVPROC glGetShaderiv = nullptr;
PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog = nullptr;
PFNGLDELETESHADERPROC glDeleteShader = nullptr;
PFNGLATTACHSHADERPROC glAttachShader = nullptr;
PFNGLCREATEPROGRAMPROC glCreateProgram = nullptr;
PFNGLLINKPROGRAMPROC glLinkProgram = nullptr;
PFNGLVALIDATEPROGRAMPROC glValidateProgram = nullptr;
PFNGLGETPROGRAMIVPROC glGetProgramiv = nullptr;
PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog = nullptr;
PFNGLUSEPROGRAMPROC glUseProgram = nullptr;
PFNGLDELETEPROGRAMPROC glDeleteProgram = nullptr;
PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation = nullptr;
PFNGLUNIFORM2FPROC glUniform2f = nullptr;
PFNGLGENVERTEXARRAYSPROC glGenVertexArrays = nullptr;
PFNGLBINDVERTEXARRAYPROC glBindVertexArray = nullptr;
PFNGLDELETEVERTEXARRAYSPROC glDeleteVertexArrays = nullptr;
PFNGLGENBUFFERSPROC glGenBuffers = nullptr;
PFNGLBINDBUFFERPROC glBindBuffer = nullptr;
PFNGLBUFFERDATAPROC glBufferData = nullptr;
PFNGLDELETEBUFFERSPROC glDeleteBuffers = nullptr;
PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer = nullptr;
PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray = nullptr;
#endif
};
} // namespace Rendering

View File

@@ -0,0 +1,585 @@
#include "core/rendering/screen.hpp"
#include <SDL3/SDL.h>
#include <ctype.h> // Para toupper
#include <algorithm> // Para max, min, transform
#include <fstream> // Para basic_ostream, operator<<, endl, basic_...
#include <iostream> // Para cerr
#include <iterator> // Para istreambuf_iterator, operator==
#include <string> // Para char_traits, string, operator+, operator==
#include "core/resources/asset.hpp" // Para Asset, AssetType
#include "core/input/mouse.hpp" // Para updateCursorVisibility
#include "game/gameplay/options.hpp" // Para Options, options, OptionsVideo, Border
#include "core/rendering/opengl/opengl_shader.hpp" // Para OpenGLShader
#include "core/resources/resource.hpp" // Para Resource
#include "core/rendering/surface.hpp" // Para Surface, readPalFile
#include "core/rendering/text.hpp" // Para Text
#include "game/ui/notifier.hpp" // Para Notifier
// [SINGLETON]
Screen* Screen::screen_ = nullptr;
// [SINGLETON] Crearemos el objeto con esta función estática
void Screen::init() {
Screen::screen_ = new Screen();
}
// [SINGLETON] Destruiremos el objeto con esta función estática
void Screen::destroy() {
delete Screen::screen_;
}
// [SINGLETON] Con este método obtenemos el objeto y podemos trabajar con él
Screen* Screen::get() {
return Screen::screen_;
}
// Constructor
Screen::Screen()
: palettes_(Asset::get()->getListByType(AssetType::PALETTE)) {
// Arranca SDL VIDEO, crea la ventana y el renderizador
initSDLVideo();
// Ajusta los tamaños
game_surface_dstrect_ = {options.video.border.width, options.video.border.height, options.game.width, options.game.height};
//adjustWindowSize();
current_palette_ = findPalette(options.video.palette);
// Define el color del borde para el modo de pantalla completa
border_color_ = static_cast<Uint8>(PaletteColor::BLACK);
// Crea la textura donde se dibujan los graficos del juego
game_texture_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, options.game.width, options.game.height);
if (!game_texture_) {
// Registrar el error si está habilitado
if (options.console) {
std::cerr << "Error: game_texture_ could not be created!\nSDL Error: " << SDL_GetError() << std::endl;
}
}
SDL_SetTextureScaleMode(game_texture_, SDL_SCALEMODE_NEAREST);
// Crea la textura donde se dibuja el borde que rodea el area de juego
border_texture_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, options.game.width + options.video.border.width * 2, options.game.height + options.video.border.height * 2);
if (!border_texture_) {
// Registrar el error si está habilitado
if (options.console) {
std::cerr << "Error: border_texture_ could not be created!\nSDL Error: " << SDL_GetError() << std::endl;
}
}
SDL_SetTextureScaleMode(border_texture_, SDL_SCALEMODE_NEAREST);
// Crea la surface donde se dibujan los graficos del juego
game_surface_ = std::make_shared<Surface>(options.game.width, options.game.height);
game_surface_->setPalette(readPalFile(palettes_.at(current_palette_)));
game_surface_->clear(static_cast<Uint8>(PaletteColor::BLACK));
// Crea la surface para el borde de colores
border_surface_ = std::make_shared<Surface>(options.game.width + options.video.border.width * 2, options.game.height + options.video.border.height * 2);
border_surface_->setPalette(readPalFile(palettes_.at(current_palette_)));
border_surface_->clear(border_color_);
// Establece la surface que actuará como renderer para recibir las llamadas a render()
renderer_surface_ = std::make_shared<std::shared_ptr<Surface>>(game_surface_);
// Extrae el nombre de las paletas desde su ruta
processPaletteList();
// Renderizar una vez la textura vacía para que tenga contenido válido
// antes de inicializar los shaders (evita pantalla negra)
SDL_RenderTexture(renderer_, game_texture_, nullptr, nullptr);
SDL_RenderTexture(renderer_, border_texture_, nullptr, nullptr);
// Ahora sí inicializar los shaders
initShaders();
}
// Destructor
Screen::~Screen() {
SDL_DestroyTexture(game_texture_);
SDL_DestroyTexture(border_texture_);
}
// Limpia el renderer
void Screen::clearRenderer(Color color) {
SDL_SetRenderDrawColor(renderer_, color.r, color.g, color.b, 0xFF);
SDL_RenderClear(renderer_);
}
// Prepara para empezar a dibujar en la textura de juego
void Screen::start() { setRendererSurface(nullptr); }
// Vuelca el contenido del renderizador en pantalla
void Screen::render() {
fps_.increment();
// Renderiza todos los overlays
renderOverlays();
// Copia la surface a la textura
surfaceToTexture();
// Copia la textura al renderizador
textureToRenderer();
}
// Establece el modo de video
void Screen::setVideoMode(bool mode) {
// Actualiza las opciones
options.video.fullscreen = mode;
// Configura el modo de pantalla y ajusta la ventana
SDL_SetWindowFullscreen(window_, options.video.fullscreen);
adjustWindowSize();
adjustRenderLogicalSize();
}
// Camibia entre pantalla completa y ventana
void Screen::toggleVideoMode() {
options.video.fullscreen = !options.video.fullscreen;
setVideoMode(options.video.fullscreen);
}
// Reduce el tamaño de la ventana
bool Screen::decWindowZoom() {
if (options.video.fullscreen == 0) {
const int PREVIOUS_ZOOM = options.window.zoom;
--options.window.zoom;
options.window.zoom = std::max(options.window.zoom, 1);
if (options.window.zoom != PREVIOUS_ZOOM) {
setVideoMode(options.video.fullscreen);
return true;
}
}
return false;
}
// Aumenta el tamaño de la ventana
bool Screen::incWindowZoom() {
if (options.video.fullscreen == 0) {
const int PREVIOUS_ZOOM = options.window.zoom;
++options.window.zoom;
options.window.zoom = std::min(options.window.zoom, options.window.max_zoom);
if (options.window.zoom != PREVIOUS_ZOOM) {
setVideoMode(options.video.fullscreen);
return true;
}
}
return false;
}
// Cambia el color del borde
void Screen::setBorderColor(Uint8 color) {
border_color_ = color;
border_surface_->clear(border_color_);
}
// Cambia entre borde visible y no visible
void Screen::toggleBorder() {
options.video.border.enabled = !options.video.border.enabled;
setVideoMode(options.video.fullscreen);
initShaders();
}
// Dibuja las notificaciones
void Screen::renderNotifications() {
if (notifications_enabled_) {
Notifier::get()->render();
}
}
// Cambia el estado de los shaders
void Screen::toggleShaders() {
options.video.shaders = !options.video.shaders;
initShaders();
}
// Actualiza la lógica de la clase
void Screen::update() {
fps_.calculate(SDL_GetTicks());
Notifier::get()->update();
Mouse::updateCursorVisibility();
}
// Calcula el tamaño de la ventana
void Screen::adjustWindowSize() {
window_width_ = options.game.width + (options.video.border.enabled ? options.video.border.width * 2 : 0);
window_height_ = options.game.height + (options.video.border.enabled ? options.video.border.height * 2 : 0);
// Establece el nuevo tamaño
if (options.video.fullscreen == 0) {
int old_width, old_height;
SDL_GetWindowSize(window_, &old_width, &old_height);
int old_pos_x, old_pos_y;
SDL_GetWindowPosition(window_, &old_pos_x, &old_pos_y);
const int NEW_POS_X = old_pos_x + (old_width - (window_width_ * options.window.zoom)) / 2;
const int NEW_POS_Y = old_pos_y + (old_height - (window_height_ * options.window.zoom)) / 2;
SDL_SetWindowSize(window_, window_width_ * options.window.zoom, window_height_ * options.window.zoom);
SDL_SetWindowPosition(window_, std::max(NEW_POS_X, WINDOWS_DECORATIONS), std::max(NEW_POS_Y, 0));
}
}
// Ajusta el tamaño lógico del renderizador
void Screen::adjustRenderLogicalSize() {
SDL_SetRenderLogicalPresentation(renderer_, window_width_, window_height_, options.video.integer_scale ? SDL_LOGICAL_PRESENTATION_INTEGER_SCALE : SDL_LOGICAL_PRESENTATION_LETTERBOX);
}
// Establece el renderizador para las surfaces
void Screen::setRendererSurface(std::shared_ptr<Surface> surface) {
(surface) ? renderer_surface_ = std::make_shared<std::shared_ptr<Surface>>(surface) : renderer_surface_ = std::make_shared<std::shared_ptr<Surface>>(game_surface_);
}
// Cambia la paleta
void Screen::nextPalette() {
++current_palette_;
if (current_palette_ == static_cast<int>(palettes_.size())) {
current_palette_ = 0;
}
setPalete();
}
// Cambia la paleta
void Screen::previousPalette() {
if (current_palette_ > 0) {
--current_palette_;
} else {
current_palette_ = static_cast<Uint8>(palettes_.size() - 1);
}
setPalete();
}
// Establece la paleta
void Screen::setPalete() {
game_surface_->loadPalette(Resource::get()->getPalette(palettes_.at(current_palette_)));
border_surface_->loadPalette(Resource::get()->getPalette(palettes_.at(current_palette_)));
options.video.palette = palettes_.at(current_palette_);
// Eliminar ".gif"
size_t pos = options.video.palette.find(".pal");
if (pos != std::string::npos) {
options.video.palette.erase(pos, 4);
}
// Convertir a mayúsculas
std::transform(options.video.palette.begin(), options.video.palette.end(), options.video.palette.begin(), ::toupper);
}
// Extrae los nombres de las paletas
void Screen::processPaletteList() {
for (auto& palette : palettes_) {
palette = getFileName(palette);
}
}
// Copia la surface a la textura
void Screen::surfaceToTexture() {
if (options.video.border.enabled) {
border_surface_->copyToTexture(renderer_, border_texture_);
game_surface_->copyToTexture(renderer_, border_texture_, nullptr, &game_surface_dstrect_);
} else {
game_surface_->copyToTexture(renderer_, game_texture_);
}
}
// Copia la textura al renderizador
void Screen::textureToRenderer() {
SDL_Texture* texture_to_render = options.video.border.enabled ? border_texture_ : game_texture_;
if (options.video.shaders && shader_backend_) {
shader_backend_->render();
} else {
SDL_SetRenderTarget(renderer_, nullptr);
SDL_SetRenderDrawColor(renderer_, 0x00, 0x00, 0x00, 0xFF);
SDL_RenderClear(renderer_);
SDL_RenderTexture(renderer_, texture_to_render, nullptr, nullptr);
SDL_RenderPresent(renderer_);
}
}
// Renderiza todos los overlays
void Screen::renderOverlays() {
renderNotifications();
renderInfo();
}
// Localiza la paleta dentro del vector de paletas
size_t Screen::findPalette(const std::string& name) {
std::string upper_name = toUpper(name + ".pal");
for (size_t i = 0; i < palettes_.size(); ++i) {
if (toUpper(getFileName(palettes_[i])) == upper_name) {
return i;
}
}
return static_cast<size_t>(0);
}
// Muestra información por pantalla
void Screen::renderInfo() {
if (show_debug_info_ && Resource::get()) {
auto text = Resource::get()->getText("smb2");
auto color = static_cast<Uint8>(PaletteColor::YELLOW);
// FPS
const std::string FPS_TEXT = std::to_string(fps_.lastValue) + " FPS";
text->writeColored(options.game.width - text->lenght(FPS_TEXT), 0, FPS_TEXT, color);
// Resolution
text->writeColored(0, 0, info_resolution_, color);
}
}
// Limpia la game_surface_
void Screen::clearSurface(Uint8 index) { game_surface_->clear(index); }
// Establece el tamaño del borde
void Screen::setBorderWidth(int width) { options.video.border.width = width; }
// Establece el tamaño del borde
void Screen::setBorderHeight(int height) { options.video.border.height = height; }
// Establece si se ha de ver el borde en el modo ventana
void Screen::setBorderEnabled(bool value) { options.video.border.enabled = value; }
// Muestra la ventana
void Screen::show() { SDL_ShowWindow(window_); }
// Oculta la ventana
void Screen::hide() { SDL_HideWindow(window_); }
// Establece la visibilidad de las notificaciones
void Screen::setNotificationsEnabled(bool value) { notifications_enabled_ = value; }
// Activa / desactiva la información de debug
void Screen::toggleDebugInfo() { show_debug_info_ = !show_debug_info_; }
// Alterna entre activar y desactivar el escalado entero
void Screen::toggleIntegerScale() {
options.video.integer_scale = !options.video.integer_scale;
SDL_SetRenderLogicalPresentation(renderer_, options.game.width, options.game.height, options.video.integer_scale ? SDL_LOGICAL_PRESENTATION_INTEGER_SCALE : SDL_LOGICAL_PRESENTATION_LETTERBOX);
}
// Getters
SDL_Renderer* Screen::getRenderer() { return renderer_; }
std::shared_ptr<Surface> Screen::getRendererSurface() { return (*renderer_surface_); }
std::shared_ptr<Surface> Screen::getBorderSurface() { return border_surface_; }
std::vector<uint8_t> loadData(const std::string& filepath) {
// Fallback a filesystem
std::ifstream file(filepath, std::ios::binary | std::ios::ate);
if (!file) {
return {};
}
std::streamsize fileSize = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<uint8_t> data(fileSize);
if (!file.read(reinterpret_cast<char*>(data.data()), fileSize)) {
return {};
}
return data;
}
// Carga el contenido de los archivos GLSL
void Screen::loadShaders() {
if (vertex_shader_source_.empty()) {
// Detectar si necesitamos OpenGL ES (Raspberry Pi)
// Intentar cargar versión ES primero si existe
std::string VERTEX_FILE = "crtpi_vertex_es.glsl";
auto data = loadData(Asset::get()->get(VERTEX_FILE));
if (data.empty()) {
// Si no existe versión ES, usar versión Desktop
VERTEX_FILE = "crtpi_vertex.glsl";
data = loadData(Asset::get()->get(VERTEX_FILE));
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Usando shaders OpenGL Desktop 3.3");
} else {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Usando shaders OpenGL ES 3.0 (Raspberry Pi)");
}
if (!data.empty()) {
vertex_shader_source_ = std::string(data.begin(), data.end());
}
}
if (fragment_shader_source_.empty()) {
// Intentar cargar versión ES primero si existe
std::string FRAGMENT_FILE = "crtpi_fragment_es.glsl";
auto data = loadData(Asset::get()->get(FRAGMENT_FILE));
if (data.empty()) {
// Si no existe versión ES, usar versión Desktop
FRAGMENT_FILE = "crtpi_fragment.glsl";
data = loadData(Asset::get()->get(FRAGMENT_FILE));
}
if (!data.empty()) {
fragment_shader_source_ = std::string(data.begin(), data.end());
}
}
}
// Inicializa los shaders
void Screen::initShaders() {
#ifndef __APPLE__
if (options.video.shaders) {
loadShaders();
if (!shader_backend_) {
shader_backend_ = std::make_unique<Rendering::OpenGLShader>();
}
shader_backend_->init(window_, options.video.border.enabled ? border_texture_ : game_texture_, vertex_shader_source_, fragment_shader_source_);
//shader_backend_->init(window_, shaders_texture_, vertex_shader_source_, fragment_shader_source_);
}
#else
// En macOS, OpenGL está deprecated y rinde mal
// TODO: Implementar backend de Metal para shaders en macOS
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Shaders no disponibles en macOS (OpenGL deprecated). Usa Metal backend.");
#endif
}
// Obtiene información sobre la pantalla
void Screen::getDisplayInfo() {
int i;
int num_displays = 0;
SDL_DisplayID* displays = SDL_GetDisplays(&num_displays);
if (displays != nullptr) {
for (i = 0; i < num_displays; ++i) {
SDL_DisplayID instance_id = displays[i];
const char* name = SDL_GetDisplayName(instance_id);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Display %" SDL_PRIu32 ": %s", instance_id, (name != nullptr) ? name : "Unknown");
}
const auto* dm = SDL_GetCurrentDisplayMode(displays[0]);
// Guarda información del monitor en display_monitor_
const char* first_display_name = SDL_GetDisplayName(displays[0]);
display_monitor_.name = (first_display_name != nullptr) ? first_display_name : "Unknown";
display_monitor_.width = static_cast<int>(dm->w);
display_monitor_.height = static_cast<int>(dm->h);
display_monitor_.refresh_rate = static_cast<int>(dm->refresh_rate);
// Calcula el máximo factor de zoom que se puede aplicar a la pantalla
options.window.max_zoom = std::min(dm->w / options.game.width, dm->h / options.game.height);
options.window.zoom = std::min(options.window.zoom, options.window.max_zoom);
// Muestra información sobre el tamaño de la pantalla y de la ventana de juego
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Current display mode: %dx%d @ %dHz", static_cast<int>(dm->w), static_cast<int>(dm->h), static_cast<int>(dm->refresh_rate));
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Window resolution: %dx%d x%d", static_cast<int>(options.game.width), static_cast<int>(options.game.height), options.window.zoom);
options.video.info = std::to_string(static_cast<int>(dm->w)) + "x" +
std::to_string(static_cast<int>(dm->h)) + " @ " +
std::to_string(static_cast<int>(dm->refresh_rate)) + " Hz";
// Calcula el máximo factor de zoom que se puede aplicar a la pantalla
const int MAX_ZOOM = std::min(dm->w / options.game.width, (dm->h - WINDOWS_DECORATIONS) / options.game.height);
// Normaliza los valores de zoom
options.window.zoom = std::min(options.window.zoom, MAX_ZOOM);
SDL_free(displays);
}
}
// Arranca SDL VIDEO y crea la ventana
auto Screen::initSDLVideo() -> bool {
// Inicializar SDL
if (!SDL_Init(SDL_INIT_VIDEO)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"FATAL: Failed to initialize SDL_VIDEO! SDL Error: %s",
SDL_GetError());
return false;
}
// Obtener información de la pantalla
getDisplayInfo();
// Configurar hint para renderizado
#ifdef __APPLE__
if (!SDL_SetHint(SDL_HINT_RENDER_DRIVER, "metal")) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Warning: Failed to set Metal hint!");
}
#else
// Configurar hint de render driver
if (!SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl")) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Warning: Failed to set OpenGL hint!");
}
#ifdef _WIN32
// Windows: Pedir explícitamente OpenGL 3.3 Core Profile
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Solicitando OpenGL 3.3 Core Profile");
#else
// Linux: Dejar que SDL elija (Desktop 3.3 en PC, ES 3.0 en RPi automáticamente)
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Usando OpenGL por defecto del sistema");
#endif
#endif
// Crear ventana
const auto WINDOW_WIDTH = options.video.border.enabled ? options.game.width + options.video.border.width * 2 : options.game.width;
const auto WINDOW_HEIGHT = options.video.border.enabled ? options.game.height + options.video.border.height * 2 : options.game.height;
#ifdef __APPLE__
SDL_WindowFlags window_flags = SDL_WINDOW_METAL;
#else
SDL_WindowFlags window_flags = SDL_WINDOW_OPENGL;
#endif
if (options.video.fullscreen) {
window_flags |= SDL_WINDOW_FULLSCREEN;
}
window_ = SDL_CreateWindow(options.window.caption.c_str(), WINDOW_WIDTH * options.window.zoom, WINDOW_HEIGHT * options.window.zoom, window_flags);
if (window_ == nullptr) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"FATAL: Failed to create window! SDL Error: %s",
SDL_GetError());
SDL_Quit();
return false;
}
// Crear renderer
renderer_ = SDL_CreateRenderer(window_, nullptr);
if (renderer_ == nullptr) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"FATAL: Failed to create renderer! SDL Error: %s",
SDL_GetError());
SDL_DestroyWindow(window_);
window_ = nullptr;
SDL_Quit();
return false;
}
// Configurar renderer
const int EXTRA_WIDTH = options.video.border.enabled ? options.video.border.width * 2 : 0;
const int EXTRA_HEIGHT = options.video.border.enabled ? options.video.border.height * 2 : 0;
SDL_SetRenderLogicalPresentation(
renderer_,
options.game.width + EXTRA_WIDTH,
options.game.height + EXTRA_HEIGHT,
options.video.integer_scale ? SDL_LOGICAL_PRESENTATION_INTEGER_SCALE : SDL_LOGICAL_PRESENTATION_LETTERBOX);
SDL_SetRenderDrawColor(renderer_, 0x00, 0x00, 0x00, 0xFF);
SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_BLEND);
SDL_SetRenderVSync(renderer_, options.video.vertical_sync ? 1 : SDL_RENDERER_VSYNC_DISABLED);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "** Video system initialized successfully");
return true;
}

View File

@@ -0,0 +1,215 @@
#pragma once
#include <SDL3/SDL.h>
#include <stddef.h> // Para size_t
#include <memory> // Para shared_ptr, __shared_ptr_access
#include <string> // Para string
#include <vector> // Para vector
#include "utils/utils.hpp" // Para Color
struct Surface;
namespace Rendering {
class ShaderBackend;
}
// Tipos de filtro
enum class ScreenFilter : Uint32 {
NEAREST = 0,
LINEAR = 1,
};
class Screen {
private:
// Constantes
static constexpr int WINDOWS_DECORATIONS = 35; // Decoraciones de la ventana
struct DisplayMonitor {
std::string name;
int width;
int height;
int refresh_rate;
};
struct FPS {
Uint32 ticks; // Tiempo en milisegundos desde que se comenzó a contar.
int frameCount; // Número acumulado de frames en el intervalo.
int lastValue; // Número de frames calculado en el último segundo.
// Constructor para inicializar la estructura.
FPS()
: ticks(0),
frameCount(0),
lastValue(0) {}
// Incrementador que se llama en cada frame.
void increment() {
frameCount++;
}
// Método para calcular y devolver el valor de FPS.
int calculate(Uint32 currentTicks) {
if (currentTicks - ticks >= 1000) // Si ha pasado un segundo o más.
{
lastValue = frameCount; // Actualizamos el valor del último FPS.
frameCount = 0; // Reiniciamos el contador de frames.
ticks = currentTicks; // Actualizamos el tiempo base.
}
return lastValue;
}
};
// [SINGLETON] Objeto privado
static Screen* screen_;
// Objetos y punteros
SDL_Window* window_; // Ventana de la aplicación
SDL_Renderer* renderer_; // El renderizador de la ventana
SDL_Texture* game_texture_; // Textura donde se dibuja el juego
SDL_Texture* border_texture_; // Textura donde se dibuja el borde del juego
std::shared_ptr<Surface> game_surface_; // Surface principal para manejar game_surface_data_
std::shared_ptr<Surface> border_surface_; // Surface para pintar el el borde de la pantalla
std::shared_ptr<std::shared_ptr<Surface>> renderer_surface_; // Puntero a la Surface que actua
std::unique_ptr<Rendering::ShaderBackend> shader_backend_; // Backend de shaders (OpenGL/Metal/Vulkan)
// Variables
int window_width_; // Ancho de la pantalla o ventana
int window_height_; // Alto de la pantalla o ventana
SDL_FRect game_surface_dstrect_; // Coordenadas donde se va a dibujar la textura del juego sobre la pantalla o ventana
Uint8 border_color_; // Color del borde añadido a la textura de juego para rellenar la pantalla
std::vector<std::string> palettes_; // Listado de los ficheros de paletta disponibles
Uint8 current_palette_ = 0; // Indice para el vector de paletas
bool notifications_enabled_ = false; // indica si se muestran las notificaciones
FPS fps_; // Variable para gestionar los frames por segundo
std::string info_resolution_; // Texto con la informacion de la pantalla
std::string vertex_shader_source_; // Almacena el vertex shader
std::string fragment_shader_source_; // Almacena el fragment shader
DisplayMonitor display_monitor_; // Informacion de la pantalla
#ifdef DEBUG
bool show_debug_info_ = false; // Indica si ha de mostrar/ocultar la información de la pantalla
#else
bool show_debug_info_ = false; // Indica si ha de mostrar/ocultar la información de la pantalla
#endif
// Dibuja las notificaciones
void renderNotifications();
// Calcula el tamaño de la ventana
void adjustWindowSize();
// Ajusta el tamaño lógico del renderizador
void adjustRenderLogicalSize();
// Extrae los nombres de las paletas
void processPaletteList();
// Copia la surface a la textura
void surfaceToTexture();
// Copia la textura al renderizador
void textureToRenderer();
// Renderiza todos los overlays
void renderOverlays();
// Localiza la paleta dentro del vector de paletas
size_t findPalette(const std::string& name);
void initShaders(); // Inicializa los shaders
void loadShaders(); // Carga el contenido del archivo GLSL
void renderInfo(); // Muestra información por pantalla
void getDisplayInfo(); // Obtiene información sobre la pantalla
auto initSDLVideo() -> bool; // Arranca SDL VIDEO y crea la ventana
// Constructor
Screen();
// Destructor
~Screen();
public:
// [SINGLETON] Crearemos el objeto con esta función estática
static void init();
// [SINGLETON] Destruiremos el objeto con esta función estática
static void destroy();
// [SINGLETON] Con este método obtenemos el objeto y podemos trabajar con él
static Screen* get();
// Limpia el renderer
void clearRenderer(Color color = {0x00, 0x00, 0x00});
// Limpia la game_surface_
void clearSurface(Uint8 index);
// Prepara para empezar a dibujar en la textura de juego
void start();
// Vuelca el contenido del renderizador en pantalla
void render();
// Actualiza la lógica de la clase
void update();
// Establece el modo de video
void setVideoMode(bool mode);
// Camibia entre pantalla completa y ventana
void toggleVideoMode();
// Alterna entre activar y desactivar el escalado entero
void toggleIntegerScale();
// Reduce el tamaño de la ventana
bool decWindowZoom();
// Aumenta el tamaño de la ventana
bool incWindowZoom();
// Cambia el color del borde
void setBorderColor(Uint8 color);
// Establece el tamaño del borde
void setBorderWidth(int width);
// Establece el tamaño del borde
void setBorderHeight(int height);
// Establece si se ha de ver el borde en el modo ventana
void setBorderEnabled(bool value);
// Cambia entre borde visible y no visible
void toggleBorder();
// Cambia el estado de los shaders
void toggleShaders();
// Muestra la ventana
void show();
// Oculta la ventana
void hide();
// Establece el renderizador para las surfaces
void setRendererSurface(std::shared_ptr<Surface> surface = nullptr);
// Cambia la paleta
void nextPalette();
void previousPalette();
// Establece la paleta
void setPalete();
// Establece la visibilidad de las notificaciones
void setNotificationsEnabled(bool value);
// Activa o desactiva la información de debug
void toggleDebugInfo();
// Getters
SDL_Renderer* getRenderer();
std::shared_ptr<Surface> getRendererSurface();
std::shared_ptr<Surface> getBorderSurface();
};

View File

@@ -0,0 +1,55 @@
#pragma once
#include <SDL3/SDL.h>
#include <string>
namespace Rendering {
/**
* @brief Interfaz abstracta para backends de renderizado con shaders
*
* Esta interfaz define el contrato que todos los backends de shaders
* deben cumplir (OpenGL, Metal, Vulkan, etc.)
*/
class ShaderBackend {
public:
virtual ~ShaderBackend() = default;
/**
* @brief Inicializa el backend de shaders
* @param window Ventana SDL
* @param texture Textura de backbuffer a la que aplicar shaders
* @param vertex_source Código fuente del vertex shader
* @param fragment_source Código fuente del fragment shader
* @return true si la inicialización fue exitosa
*/
virtual bool init(SDL_Window* window,
SDL_Texture* texture,
const std::string& vertex_source,
const std::string& fragment_source) = 0;
/**
* @brief Renderiza la textura con los shaders aplicados
*/
virtual void render() = 0;
/**
* @brief Establece el tamaño de la textura como parámetro del shader
* @param width Ancho de la textura
* @param height Alto de la textura
*/
virtual void setTextureSize(float width, float height) = 0;
/**
* @brief Limpia y libera recursos del backend
*/
virtual void cleanup() = 0;
/**
* @brief Verifica si el backend está usando aceleración por hardware
* @return true si usa aceleración (OpenGL/Metal/Vulkan)
*/
virtual bool isHardwareAccelerated() const = 0;
};
} // namespace Rendering

View File

@@ -0,0 +1,564 @@
// IWYU pragma: no_include <bits/std_abs.h>
#include "core/rendering/surface.hpp"
#include <SDL3/SDL.h>
#include <algorithm> // Para min, max, copy_n, fill
#include <cmath> // Para abs
#include <cstdint> // Para uint32_t
#include <cstring> // Para memcpy, size_t
#include <fstream> // Para basic_ifstream, basic_ostream, basic_ist...
#include <iostream> // Para cerr
#include <memory> // Para shared_ptr, __shared_ptr_access, default...
#include <sstream> // Para basic_istringstream
#include <stdexcept> // Para runtime_error
#include <vector> // Para vector
#include "core/rendering/gif.hpp" // Para Gif
#include "core/rendering/screen.hpp" // Para Screen
// Carga una paleta desde un archivo .gif
Palette loadPalette(const std::string& file_path) {
// Abrir el archivo en modo binario
std::ifstream file(file_path, std::ios::binary | std::ios::ate);
if (!file.is_open()) {
throw std::runtime_error("Error opening file: " + file_path);
}
// Obtener el tamaño del archivo y leerlo en un buffer
std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<Uint8> buffer(size);
if (!file.read(reinterpret_cast<char*>(buffer.data()), size)) {
throw std::runtime_error("Error reading file: " + file_path);
}
// Cargar la paleta usando los datos del buffer
GIF::Gif gif;
std::vector<uint32_t> pal = gif.loadPalette(buffer.data());
if (pal.empty()) {
throw std::runtime_error("No palette found in GIF file: " + file_path);
}
// Crear la paleta y copiar los datos desde 'pal'
Palette palette = {}; // Inicializa la paleta con ceros
std::copy_n(pal.begin(), std::min(pal.size(), palette.size()), palette.begin());
// Mensaje de depuración
printWithDots("Palette : ", file_path.substr(file_path.find_last_of("\\/") + 1), "[ LOADED ]");
return palette;
}
// Carga una paleta desde un archivo .pal
Palette readPalFile(const std::string& file_path) {
Palette palette{};
palette.fill(0); // Inicializar todo con 0 (transparente por defecto)
std::ifstream file(file_path);
if (!file.is_open()) {
throw std::runtime_error("No se pudo abrir el archivo .pal");
}
std::string line;
int line_number = 0;
int color_index = 0;
while (std::getline(file, line)) {
++line_number;
// Ignorar las tres primeras líneas del archivo
if (line_number <= 3) {
continue;
}
// Procesar las líneas restantes con valores RGB
std::istringstream ss(line);
int r, g, b;
if (ss >> r >> g >> b) {
// Construir el color ARGB (A = 255 por defecto)
Uint32 color = (255 << 24) | (r << 16) | (g << 8) | b;
palette[color_index++] = color;
// Limitar a un máximo de 256 colores (opcional)
if (color_index >= 256) {
break;
}
}
}
file.close();
return palette;
}
// Constructor
Surface::Surface(int w, int h)
: surface_data_(std::make_shared<SurfaceData>(w, h)),
transparent_color_(static_cast<Uint8>(PaletteColor::TRANSPARENT)) { initializeSubPalette(sub_palette_); }
Surface::Surface(const std::string& file_path)
: transparent_color_(static_cast<Uint8>(PaletteColor::TRANSPARENT)) {
SurfaceData loadedData = loadSurface(file_path);
surface_data_ = std::make_shared<SurfaceData>(std::move(loadedData));
initializeSubPalette(sub_palette_);
}
// Carga una superficie desde un archivo
SurfaceData Surface::loadSurface(const std::string& file_path) {
// Abrir el archivo usando std::ifstream para manejo automático del recurso
std::ifstream file(file_path, std::ios::binary | std::ios::ate);
if (!file.is_open()) {
std::cerr << "Error opening file: " << file_path << std::endl;
throw std::runtime_error("Error opening file");
}
// Obtener el tamaño del archivo
std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);
// Leer el contenido del archivo en un buffer
std::vector<Uint8> buffer(size);
if (!file.read(reinterpret_cast<char*>(buffer.data()), size)) {
std::cerr << "Error reading file: " << file_path << std::endl;
throw std::runtime_error("Error reading file");
}
// Crear un objeto Gif y llamar a la función loadGif
GIF::Gif gif;
Uint16 w = 0, h = 0;
std::vector<Uint8> rawPixels = gif.loadGif(buffer.data(), w, h);
if (rawPixels.empty()) {
std::cerr << "Error loading GIF from file: " << file_path << std::endl;
throw std::runtime_error("Error loading GIF");
}
// Si el constructor de Surface espera un std::shared_ptr<Uint8[]>,
// reservamos un bloque dinámico y copiamos los datos del vector.
size_t pixelCount = rawPixels.size();
auto pixels = std::shared_ptr<Uint8[]>(new Uint8[pixelCount], std::default_delete<Uint8[]>());
std::memcpy(pixels.get(), rawPixels.data(), pixelCount);
// Crear y devolver directamente el objeto SurfaceData
printWithDots("Surface : ", file_path.substr(file_path.find_last_of("\\/") + 1), "[ LOADED ]");
return SurfaceData(w, h, pixels);
}
// Carga una paleta desde un archivo
void Surface::loadPalette(const std::string& file_path) {
palette_ = ::loadPalette(file_path);
}
// Carga una paleta desde otra paleta
void Surface::loadPalette(Palette palette) {
palette_ = palette;
}
// Establece un color en la paleta
void Surface::setColor(int index, Uint32 color) {
palette_.at(index) = color;
}
// Rellena la superficie con un color
void Surface::clear(Uint8 color) {
const size_t total_pixels = surface_data_->width * surface_data_->height;
Uint8* data_ptr = surface_data_->data.get();
std::fill(data_ptr, data_ptr + total_pixels, color);
}
// Pone un pixel en la SurfaceData
void Surface::putPixel(int x, int y, Uint8 color) {
if (x < 0 || y < 0 || x >= surface_data_->width || y >= surface_data_->height) {
return; // Coordenadas fuera de rango
}
const int index = x + y * surface_data_->width;
surface_data_->data.get()[index] = color;
}
// Obtiene el color de un pixel de la surface_data
Uint8 Surface::getPixel(int x, int y) { return surface_data_->data.get()[x + y * static_cast<int>(surface_data_->width)]; }
// Dibuja un rectangulo relleno
void Surface::fillRect(const SDL_FRect* rect, Uint8 color) {
// Limitar los valores del rectángulo al tamaño de la superficie
float x_start = std::max(0.0F, rect->x);
float y_start = std::max(0.0F, rect->y);
float x_end = std::min(rect->x + rect->w, surface_data_->width);
float y_end = std::min(rect->y + rect->h, surface_data_->height);
// Recorrer cada píxel dentro del rectángulo directamente
for (int y = y_start; y < y_end; ++y) {
for (int x = x_start; x < x_end; ++x) {
const int INDEX = x + y * surface_data_->width;
surface_data_->data.get()[INDEX] = color;
}
}
}
// Dibuja el borde de un rectangulo
void Surface::drawRectBorder(const SDL_FRect* rect, Uint8 color) {
// Limitar los valores del rectángulo al tamaño de la superficie
float x_start = std::max(0.0F, rect->x);
float y_start = std::max(0.0F, rect->y);
float x_end = std::min(rect->x + rect->w, surface_data_->width);
float y_end = std::min(rect->y + rect->h, surface_data_->height);
// Dibujar bordes horizontales
for (int x = x_start; x < x_end; ++x) {
// Borde superior
const int top_index = x + y_start * surface_data_->width;
surface_data_->data.get()[top_index] = color;
// Borde inferior
const int bottom_index = x + (y_end - 1) * surface_data_->width;
surface_data_->data.get()[bottom_index] = color;
}
// Dibujar bordes verticales
for (int y = y_start; y < y_end; ++y) {
// Borde izquierdo
const int LEFT_INDEX = x_start + y * surface_data_->width;
surface_data_->data.get()[LEFT_INDEX] = color;
// Borde derecho
const int RIGHT_INDEX = (x_end - 1) + y * surface_data_->width;
surface_data_->data.get()[RIGHT_INDEX] = color;
}
}
// Dibuja una linea
void Surface::drawLine(float x1, float y1, float x2, float y2, Uint8 color) {
// Calcula las diferencias
float dx = std::abs(x2 - x1);
float dy = std::abs(y2 - y1);
// Determina la dirección del incremento
float sx = (x1 < x2) ? 1 : -1;
float sy = (y1 < y2) ? 1 : -1;
float err = dx - dy;
while (true) {
// Asegúrate de no dibujar fuera de los límites de la superficie
if (x1 >= 0 && x1 < surface_data_->width && y1 >= 0 && y1 < surface_data_->height) {
surface_data_->data.get()[static_cast<size_t>(x1 + y1 * surface_data_->width)] = color;
}
// Si alcanzamos el punto final, salimos
if (x1 == x2 && y1 == y2)
break;
int e2 = 2 * err;
if (e2 > -dy) {
err -= dy;
x1 += sx;
}
if (e2 < dx) {
err += dx;
y1 += sy;
}
}
}
void Surface::render(float dx, float dy, float sx, float sy, float w, float h) {
auto surface_data = Screen::get()->getRendererSurface()->getSurfaceData();
// Limitar la región para evitar accesos fuera de rango en origen
w = std::min(w, surface_data_->width - sx);
h = std::min(h, surface_data_->height - sy);
// Limitar la región para evitar accesos fuera de rango en destino
w = std::min(w, surface_data->width - dx);
h = std::min(h, surface_data->height - dy);
for (int iy = 0; iy < h; ++iy) {
for (int ix = 0; ix < w; ++ix) {
// Verificar que las coordenadas de destino están dentro de los límites
if (int dest_x = dx + ix; dest_x >= 0 && dest_x < surface_data->width) {
if (int dest_y = dy + iy; dest_y >= 0 && dest_y < surface_data->height) {
int src_x = sx + ix;
int src_y = sy + iy;
Uint8 color = surface_data_->data.get()[static_cast<size_t>(src_x + src_y * surface_data_->width)];
if (color != transparent_color_) {
surface_data->data.get()[static_cast<size_t>(dest_x + dest_y * surface_data->width)] = sub_palette_[color];
}
}
}
}
}
}
void Surface::render(int x, int y, SDL_FRect* srcRect, SDL_FlipMode flip) {
auto surface_data_dest = Screen::get()->getRendererSurface()->getSurfaceData();
// Determina la región de origen (clip) a renderizar
float sx = (srcRect) ? srcRect->x : 0;
float sy = (srcRect) ? srcRect->y : 0;
float w = (srcRect) ? srcRect->w : surface_data_->width;
float h = (srcRect) ? srcRect->h : surface_data_->height;
// Limitar la región para evitar accesos fuera de rango en origen
w = std::min(w, surface_data_->width - sx);
h = std::min(h, surface_data_->height - sy);
w = std::min(w, surface_data_dest->width - x);
h = std::min(h, surface_data_dest->height - y);
// Limitar la región para evitar accesos fuera de rango en destino
w = std::min(w, surface_data_dest->width - x);
h = std::min(h, surface_data_dest->height - y);
// Renderiza píxel por píxel aplicando el flip si es necesario
for (int iy = 0; iy < h; ++iy) {
for (int ix = 0; ix < w; ++ix) {
// Coordenadas de origen
int src_x = (flip == SDL_FLIP_HORIZONTAL) ? (sx + w - 1 - ix) : (sx + ix);
int src_y = (flip == SDL_FLIP_VERTICAL) ? (sy + h - 1 - iy) : (sy + iy);
// Coordenadas de destino
int dest_x = x + ix;
int dest_y = y + iy;
// Verificar que las coordenadas de destino están dentro de los límites
if (dest_x >= 0 && dest_x < surface_data_dest->width && dest_y >= 0 && dest_y < surface_data_dest->height) {
// Copia el píxel si no es transparente
Uint8 color = surface_data_->data.get()[static_cast<size_t>(src_x + src_y * surface_data_->width)];
if (color != transparent_color_) {
surface_data_dest->data[dest_x + dest_y * surface_data_dest->width] = sub_palette_[color];
}
}
}
}
}
// Copia una región de la superficie de origen a la de destino
void Surface::render(SDL_FRect* srcRect, SDL_FRect* dstRect, SDL_FlipMode flip) {
auto surface_data = Screen::get()->getRendererSurface()->getSurfaceData();
// Si srcRect es nullptr, tomar toda la superficie fuente
float sx = (srcRect) ? srcRect->x : 0;
float sy = (srcRect) ? srcRect->y : 0;
float sw = (srcRect) ? srcRect->w : surface_data_->width;
float sh = (srcRect) ? srcRect->h : surface_data_->height;
// Si dstRect es nullptr, asignar las mismas dimensiones que srcRect
float dx = (dstRect) ? dstRect->x : 0;
float dy = (dstRect) ? dstRect->y : 0;
float dw = (dstRect) ? dstRect->w : sw;
float dh = (dstRect) ? dstRect->h : sh;
// Asegurarse de que srcRect y dstRect tienen las mismas dimensiones
if (sw != dw || sh != dh) {
dw = sw; // Respetar las dimensiones de srcRect
dh = sh;
}
// Limitar la región para evitar accesos fuera de rango en src y dst
sw = std::min(sw, surface_data_->width - sx);
sh = std::min(sh, surface_data_->height - sy);
dw = std::min(dw, surface_data->width - dx);
dh = std::min(dh, surface_data->height - dy);
int final_width = std::min(sw, dw);
int final_height = std::min(sh, dh);
// Renderiza píxel por píxel aplicando el flip si es necesario
for (int iy = 0; iy < final_height; ++iy) {
for (int ix = 0; ix < final_width; ++ix) {
// Coordenadas de origen
int src_x = (flip == SDL_FLIP_HORIZONTAL) ? (sx + final_width - 1 - ix) : (sx + ix);
int src_y = (flip == SDL_FLIP_VERTICAL) ? (sy + final_height - 1 - iy) : (sy + iy);
// Coordenadas de destino
if (int dest_x = dx + ix; dest_x >= 0 && dest_x < surface_data->width) {
if (int dest_y = dy + iy; dest_y >= 0 && dest_y < surface_data->height) {
// Copiar el píxel si no es transparente
Uint8 color = surface_data_->data.get()[static_cast<size_t>(src_x + src_y * surface_data_->width)];
if (color != transparent_color_) {
surface_data->data[dest_x + dest_y * surface_data->width] = sub_palette_[color];
}
}
}
}
}
}
// Copia una región de la SurfaceData de origen a la SurfaceData de destino reemplazando un color por otro
void Surface::renderWithColorReplace(int x, int y, Uint8 source_color, Uint8 target_color, SDL_FRect* srcRect, SDL_FlipMode flip) {
auto surface_data = Screen::get()->getRendererSurface()->getSurfaceData();
// Determina la región de origen (clip) a renderizar
float sx = (srcRect) ? srcRect->x : 0;
float sy = (srcRect) ? srcRect->y : 0;
float w = (srcRect) ? srcRect->w : surface_data_->width;
float h = (srcRect) ? srcRect->h : surface_data_->height;
// Limitar la región para evitar accesos fuera de rango
w = std::min(w, surface_data_->width - sx);
h = std::min(h, surface_data_->height - sy);
// Renderiza píxel por píxel aplicando el flip si es necesario
for (int iy = 0; iy < h; ++iy) {
for (int ix = 0; ix < w; ++ix) {
// Coordenadas de origen
int src_x = (flip == SDL_FLIP_HORIZONTAL) ? (sx + w - 1 - ix) : (sx + ix);
int src_y = (flip == SDL_FLIP_VERTICAL) ? (sy + h - 1 - iy) : (sy + iy);
// Coordenadas de destino
int dest_x = x + ix;
int dest_y = y + iy;
// Verifica que las coordenadas de destino estén dentro de los límites
if (dest_x < 0 || dest_y < 0 || dest_x >= surface_data->width || dest_y >= surface_data->height) {
continue; // Saltar píxeles fuera del rango del destino
}
// Copia el píxel si no es transparente
Uint8 color = surface_data_->data.get()[static_cast<size_t>(src_x + src_y * surface_data_->width)];
if (color != transparent_color_) {
surface_data->data[dest_x + dest_y * surface_data->width] =
(color == source_color) ? target_color : color;
}
}
}
}
// Vuelca la superficie a una textura
void Surface::copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture) {
if (!renderer || !texture || !surface_data_) {
throw std::runtime_error("Renderer or texture is null.");
}
if (surface_data_->width <= 0 || surface_data_->height <= 0 || !surface_data_->data.get()) {
throw std::runtime_error("Invalid surface dimensions or data.");
}
Uint32* pixels = nullptr;
int pitch = 0;
// Bloquea la textura para modificar los píxeles directamente
if (!SDL_LockTexture(texture, nullptr, reinterpret_cast<void**>(&pixels), &pitch)) {
throw std::runtime_error("Failed to lock texture: " + std::string(SDL_GetError()));
}
// Convertir `pitch` de bytes a Uint32 (asegurando alineación correcta en hardware)
int row_stride = pitch / sizeof(Uint32);
for (int y = 0; y < surface_data_->height; ++y) {
for (int x = 0; x < surface_data_->width; ++x) {
// Calcular la posición correcta en la textura teniendo en cuenta el stride
int texture_index = y * row_stride + x;
int surface_index = y * surface_data_->width + x;
pixels[texture_index] = palette_[surface_data_->data.get()[surface_index]];
}
}
SDL_UnlockTexture(texture); // Desbloquea la textura
// Renderiza la textura en la pantalla completa
if (!SDL_RenderTexture(renderer, texture, nullptr, nullptr)) {
throw std::runtime_error("Failed to copy texture to renderer: " + std::string(SDL_GetError()));
}
}
// Vuelca la superficie a una textura
void Surface::copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture, SDL_FRect* srcRect, SDL_FRect* destRect) {
if (!renderer || !texture || !surface_data_) {
throw std::runtime_error("Renderer or texture is null.");
}
if (surface_data_->width <= 0 || surface_data_->height <= 0 || !surface_data_->data.get()) {
throw std::runtime_error("Invalid surface dimensions or data.");
}
Uint32* pixels = nullptr;
int pitch = 0;
SDL_Rect lockRect;
if (destRect) {
lockRect.x = static_cast<int>(destRect->x);
lockRect.y = static_cast<int>(destRect->y);
lockRect.w = static_cast<int>(destRect->w);
lockRect.h = static_cast<int>(destRect->h);
}
// Usa lockRect solo si destRect no es nulo
if (!SDL_LockTexture(texture, destRect ? &lockRect : nullptr, reinterpret_cast<void**>(&pixels), &pitch)) {
throw std::runtime_error("Failed to lock texture: " + std::string(SDL_GetError()));
}
int row_stride = pitch / sizeof(Uint32);
for (int y = 0; y < surface_data_->height; ++y) {
for (int x = 0; x < surface_data_->width; ++x) {
int texture_index = y * row_stride + x;
int surface_index = y * surface_data_->width + x;
pixels[texture_index] = palette_[surface_data_->data.get()[surface_index]];
}
}
SDL_UnlockTexture(texture);
// Renderiza la textura con los rectángulos especificados
if (!SDL_RenderTexture(renderer, texture, srcRect, destRect)) {
throw std::runtime_error("Failed to copy texture to renderer: " + std::string(SDL_GetError()));
}
}
// Realiza un efecto de fundido en la paleta principal
bool Surface::fadePalette() {
// Verificar que el tamaño mínimo de palette_ sea adecuado
static constexpr int palette_size = 19;
if (sizeof(palette_) / sizeof(palette_[0]) < palette_size) {
throw std::runtime_error("Palette size is insufficient for fadePalette operation.");
}
// Desplazar colores (pares e impares)
for (int i = 18; i > 1; --i) {
palette_[i] = palette_[i - 2];
}
// Ajustar el primer color
palette_[1] = palette_[0];
// Devolver si el índice 15 coincide con el índice 0
return palette_[15] == palette_[0];
}
// Realiza un efecto de fundido en la paleta secundaria
bool Surface::fadeSubPalette(Uint32 delay) {
// Variable estática para almacenar el último tick
static Uint32 last_tick = 0;
// Obtener el tiempo actual
Uint32 current_tick = SDL_GetTicks();
// Verificar si ha pasado el tiempo de retardo
if (current_tick - last_tick < delay) {
return false; // No se realiza el fade
}
// Actualizar el último tick
last_tick = current_tick;
// Verificar que el tamaño mínimo de sub_palette_ sea adecuado
static constexpr int sub_palette_size = 19;
if (sizeof(sub_palette_) / sizeof(sub_palette_[0]) < sub_palette_size) {
throw std::runtime_error("Palette size is insufficient for fadePalette operation.");
}
// Desplazar colores (pares e impares)
for (int i = 18; i > 1; --i) {
sub_palette_[i] = sub_palette_[i - 2];
}
// Ajustar el primer color
sub_palette_[1] = sub_palette_[0];
// Devolver si el índice 15 coincide con el índice 0
return sub_palette_[15] == sub_palette_[0];
}

View File

@@ -0,0 +1,133 @@
#pragma once
#include <SDL3/SDL.h>
#include <array> // Para array
#include <memory> // Para default_delete, shared_ptr, __shared_pt...
#include <numeric> // Para iota
#include <string> // Para string
#include <utility> // Para move
#include "utils/utils.hpp" // Para PaletteColor
// Alias
using Palette = std::array<Uint32, 256>;
using SubPalette = std::array<Uint8, 256>;
// Carga una paleta desde un archivo .gif
Palette loadPalette(const std::string& file_path);
// Carga una paleta desde un archivo .pal
Palette readPalFile(const std::string& file_path);
struct SurfaceData {
std::shared_ptr<Uint8[]> data; // Usa std::shared_ptr para gestión automática
float width; // Ancho de la imagen
float height; // Alto de la imagen
// Constructor por defecto
SurfaceData()
: data(nullptr),
width(0),
height(0) {}
// Constructor que inicializa dimensiones y asigna memoria
SurfaceData(float w, float h)
: data(std::shared_ptr<Uint8[]>(new Uint8[static_cast<size_t>(w * h)](), std::default_delete<Uint8[]>())),
width(w),
height(h) {}
// Constructor para inicializar directamente con datos
SurfaceData(float w, float h, std::shared_ptr<Uint8[]> pixels)
: data(std::move(pixels)),
width(w),
height(h) {}
// Constructor de movimiento
SurfaceData(SurfaceData&& other) noexcept = default;
// Operador de movimiento
SurfaceData& operator=(SurfaceData&& other) noexcept = default;
// Evita copias accidentales
SurfaceData(const SurfaceData&) = delete;
SurfaceData& operator=(const SurfaceData&) = delete;
};
class Surface {
private:
std::shared_ptr<SurfaceData> surface_data_; // Datos a dibujar
Palette palette_; // Paleta para volcar la SurfaceData a una Textura
SubPalette sub_palette_; // Paleta para reindexar colores
int transparent_color_; // Indice de la paleta que se omite en la copia de datos
public:
// Constructor
Surface(int w, int h);
explicit Surface(const std::string& file_path);
// Destructor
~Surface() = default;
// Carga una SurfaceData desde un archivo
SurfaceData loadSurface(const std::string& file_path);
// Carga una paleta desde un archivo
void loadPalette(const std::string& file_path);
void loadPalette(Palette palette);
// Copia una región de la SurfaceData de origen a la SurfaceData de destino
void render(float dx, float dy, float sx, float sy, float w, float h);
void render(int x, int y, SDL_FRect* clip = nullptr, SDL_FlipMode flip = SDL_FLIP_NONE);
void render(SDL_FRect* srcRect = nullptr, SDL_FRect* dstRect = nullptr, SDL_FlipMode flip = SDL_FLIP_NONE);
// Copia una región de la SurfaceData de origen a la SurfaceData de destino reemplazando un color por otro
void renderWithColorReplace(int x, int y, Uint8 source_color = 0, Uint8 target_color = 0, SDL_FRect* srcRect = nullptr, SDL_FlipMode flip = SDL_FLIP_NONE);
// Establece un color en la paleta
void setColor(int index, Uint32 color);
// Rellena la SurfaceData con un color
void clear(Uint8 color);
// Vuelca la SurfaceData a una textura
void copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture);
void copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture, SDL_FRect* srcRect, SDL_FRect* destRect);
// Realiza un efecto de fundido en las paletas
bool fadePalette();
bool fadeSubPalette(Uint32 delay = 0);
// Pone un pixel en la SurfaceData
void putPixel(int x, int y, Uint8 color);
// Obtiene el color de un pixel de la surface_data
Uint8 getPixel(int x, int y);
// Dibuja un rectangulo relleno
void fillRect(const SDL_FRect* rect, Uint8 color);
// Dibuja el borde de un rectangulo
void drawRectBorder(const SDL_FRect* rect, Uint8 color);
// Dibuja una linea
void drawLine(float x1, float y1, float x2, float y2, Uint8 color);
// Metodos para gestionar surface_data_
std::shared_ptr<SurfaceData> getSurfaceData() const { return surface_data_; }
void setSurfaceData(std::shared_ptr<SurfaceData> new_data) { surface_data_ = new_data; }
// Obtien ancho y alto
float getWidth() const { return surface_data_->width; }
float getHeight() const { return surface_data_->height; }
// Color transparente
Uint8 getTransparentColor() const { return transparent_color_; }
void setTransparentColor(Uint8 color = 255) { transparent_color_ = color; }
// Paleta
void setPalette(const std::array<Uint32, 256>& palette) { palette_ = palette; }
// Inicializa la sub paleta
void initializeSubPalette(SubPalette& palette) { std::iota(palette.begin(), palette.end(), 0); }
};

View File

@@ -0,0 +1,236 @@
#include "core/rendering/surface_animated_sprite.hpp"
#include <stddef.h> // Para size_t
#include <fstream> // Para basic_ostream, basic_istream, operator<<, basic...
#include <iostream> // Para cout, cerr
#include <sstream> // Para basic_stringstream
#include <stdexcept> // Para runtime_error
#include "core/rendering/surface.hpp" // Para Surface
#include "utils/utils.hpp" // Para printWithDots
// Carga las animaciones en un vector(Animations) desde un fichero
Animations loadAnimationsFromFile(const std::string& file_path) {
std::ifstream file(file_path);
if (!file) {
std::cerr << "Error: Fichero no encontrado " << file_path << std::endl;
throw std::runtime_error("Fichero no encontrado: " + file_path);
}
printWithDots("Animation : ", file_path.substr(file_path.find_last_of("\\/") + 1), "[ LOADED ]");
std::vector<std::string> buffer;
std::string line;
while (std::getline(file, line)) {
if (!line.empty())
buffer.push_back(line);
}
return buffer;
}
// Constructor
SAnimatedSprite::SAnimatedSprite(std::shared_ptr<Surface> surface, const std::string& file_path)
: SMovingSprite(surface) {
// Carga las animaciones
if (!file_path.empty()) {
Animations v = loadAnimationsFromFile(file_path);
setAnimations(v);
}
}
// Constructor
SAnimatedSprite::SAnimatedSprite(std::shared_ptr<Surface> surface, const Animations& animations)
: SMovingSprite(surface) {
if (!animations.empty()) {
setAnimations(animations);
}
}
// Obtiene el indice de la animación a partir del nombre
int SAnimatedSprite::getIndex(const std::string& name) {
auto index = -1;
for (const auto& a : animations_) {
index++;
if (a.name == name) {
return index;
}
}
std::cout << "** Warning: could not find \"" << name.c_str() << "\" animation" << std::endl;
return -1;
}
// Calcula el frame correspondiente a la animación
void SAnimatedSprite::animate() {
if (animations_[current_animation_].speed == 0) {
return;
}
// Calcula el frame actual a partir del contador
animations_[current_animation_].current_frame = animations_[current_animation_].counter / animations_[current_animation_].speed;
// Si alcanza el final de la animación, reinicia el contador de la animación
// en función de la variable loop y coloca el nuevo frame
if (animations_[current_animation_].current_frame >= static_cast<int>(animations_[current_animation_].frames.size())) {
if (animations_[current_animation_].loop == -1) { // Si no hay loop, deja el último frame
animations_[current_animation_].current_frame = animations_[current_animation_].frames.size();
animations_[current_animation_].completed = true;
} else { // Si hay loop, vuelve al frame indicado
animations_[current_animation_].counter = 0;
animations_[current_animation_].current_frame = animations_[current_animation_].loop;
}
}
// En caso contrario
else {
// Escoge el frame correspondiente de la animación
setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]);
// Incrementa el contador de la animacion
animations_[current_animation_].counter++;
}
}
// Comprueba si ha terminado la animación
bool SAnimatedSprite::animationIsCompleted() {
return animations_[current_animation_].completed;
}
// Establece la animacion actual
void SAnimatedSprite::setCurrentAnimation(const std::string& name) {
const auto new_animation = getIndex(name);
if (current_animation_ != new_animation) {
current_animation_ = new_animation;
animations_[current_animation_].current_frame = 0;
animations_[current_animation_].counter = 0;
animations_[current_animation_].completed = false;
setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]);
}
}
// Establece la animacion actual
void SAnimatedSprite::setCurrentAnimation(int index) {
const auto new_animation = index;
if (current_animation_ != new_animation) {
current_animation_ = new_animation;
animations_[current_animation_].current_frame = 0;
animations_[current_animation_].counter = 0;
animations_[current_animation_].completed = false;
setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]);
}
}
// Actualiza las variables del objeto
void SAnimatedSprite::update() {
animate();
SMovingSprite::update();
}
// Reinicia la animación
void SAnimatedSprite::resetAnimation() {
animations_[current_animation_].current_frame = 0;
animations_[current_animation_].counter = 0;
animations_[current_animation_].completed = false;
}
// Carga la animación desde un vector de cadenas
void SAnimatedSprite::setAnimations(const Animations& animations) {
float frame_width = 1.0F;
float frame_height = 1.0F;
int frames_per_row = 1;
int max_tiles = 1;
size_t index = 0;
while (index < animations.size()) {
std::string line = animations.at(index);
// Parsea el fichero para buscar variables y valores
if (line != "[animation]") {
// Encuentra la posición del caracter '='
size_t pos = line.find("=");
// Procesa las dos subcadenas
if (pos != std::string::npos) {
std::string key = line.substr(0, pos);
int value = std::stoi(line.substr(pos + 1));
if (key == "frame_width")
frame_width = value;
else if (key == "frame_height")
frame_height = value;
else
std::cout << "Warning: unknown parameter " << key << std::endl;
frames_per_row = surface_->getWidth() / frame_width;
const int w = surface_->getWidth() / frame_width;
const int h = surface_->getHeight() / frame_height;
max_tiles = w * h;
}
}
// Si la linea contiene el texto [animation] se realiza el proceso de carga de una animación
if (line == "[animation]") {
AnimationData animation;
do {
index++;
line = animations.at(index);
size_t pos = line.find("=");
if (pos != std::string::npos) {
std::string key = line.substr(0, pos);
std::string value = line.substr(pos + 1);
if (key == "name")
animation.name = value;
else if (key == "speed")
animation.speed = std::stoi(value);
else if (key == "loop")
animation.loop = std::stoi(value);
else if (key == "frames") {
// Se introducen los valores separados por comas en un vector
std::stringstream ss(value);
std::string tmp;
SDL_FRect rect = {0.0F, 0.0F, frame_width, frame_height};
while (getline(ss, tmp, ',')) {
// Comprueba que el tile no sea mayor que el maximo indice permitido
const int num_tile = std::stoi(tmp);
if (num_tile <= max_tiles) {
rect.x = (num_tile % frames_per_row) * frame_width;
rect.y = (num_tile / frames_per_row) * frame_height;
animation.frames.emplace_back(rect);
}
}
}
else
std::cout << "Warning: unknown parameter " << key << std::endl;
}
} while (line != "[/animation]");
// Añade la animación al vector de animaciones
animations_.emplace_back(animation);
}
// Una vez procesada la linea, aumenta el indice para pasar a la siguiente
index++;
}
// Pone un valor por defecto
setWidth(frame_width);
setHeight(frame_height);
}
// Establece el frame actual de la animación
void SAnimatedSprite::setCurrentAnimationFrame(int num) {
// Descarta valores fuera de rango
if (num < 0 || num >= static_cast<int>(animations_[current_animation_].frames.size())) {
num = 0;
}
// Cambia el valor de la variable
animations_[current_animation_].current_frame = num;
animations_[current_animation_].counter = 0;
// Escoge el frame correspondiente de la animación
setClip(animations_[current_animation_].frames[animations_[current_animation_].current_frame]);
}

View File

@@ -0,0 +1,78 @@
#pragma once
#include <SDL3/SDL.h>
#include <memory> // Para shared_ptr
#include <string> // Para string
#include <vector> // Para vector
#include "core/rendering/surface_moving_sprite.hpp" // Para SMovingSprite
class Surface; // lines 9-9
struct AnimationData {
std::string name; // Nombre de la animacion
std::vector<SDL_FRect> frames; // Cada uno de los frames que componen la animación
int speed; // Velocidad de la animación
int loop; // Indica a que frame vuelve la animación al terminar. -1 para que no vuelva
bool completed; // Indica si ha finalizado la animación
int current_frame; // Frame actual
int counter; // Contador para las animaciones
AnimationData()
: name(std::string()),
speed(5),
loop(0),
completed(false),
current_frame(0),
counter(0) {}
};
using Animations = std::vector<std::string>;
// Carga las animaciones en un vector(Animations) desde un fichero
Animations loadAnimationsFromFile(const std::string& file_path);
class SAnimatedSprite : public SMovingSprite {
protected:
// Variables
std::vector<AnimationData> animations_; // Vector con las diferentes animaciones
int current_animation_ = 0; // Animacion activa
// Calcula el frame correspondiente a la animación actual
void animate();
// Carga la animación desde un vector de cadenas
void setAnimations(const Animations& animations);
public:
// Constructor
SAnimatedSprite(std::shared_ptr<Surface> surface, const std::string& file_path);
SAnimatedSprite(std::shared_ptr<Surface> surface, const Animations& animations);
explicit SAnimatedSprite(std::shared_ptr<Surface> surface)
: SMovingSprite(surface) {}
// Destructor
virtual ~SAnimatedSprite() override = default;
// Actualiza las variables del objeto
void update() override;
// Comprueba si ha terminado la animación
bool animationIsCompleted();
// Obtiene el indice de la animación a partir del nombre
int getIndex(const std::string& name);
// Establece la animacion actual
void setCurrentAnimation(const std::string& name = "default");
void setCurrentAnimation(int index = 0);
// Reinicia la animación
void resetAnimation();
// Establece el frame actual de la animación
void setCurrentAnimationFrame(int num);
// Obtiene el numero de frames de la animación actual
int getCurrentAnimationSize() { return static_cast<int>(animations_[current_animation_].frames.size()); }
};

View File

@@ -0,0 +1,94 @@
#include "core/rendering/surface_moving_sprite.hpp"
#include "core/rendering/surface.hpp" // Para Surface
// Constructor
SMovingSprite::SMovingSprite(std::shared_ptr<Surface> surface, SDL_FRect pos, SDL_FlipMode flip)
: SSprite(surface, pos),
x_(pos.x),
y_(pos.y),
flip_(flip) { SSprite::pos_ = pos; }
SMovingSprite::SMovingSprite(std::shared_ptr<Surface> surface, SDL_FRect pos)
: SSprite(surface, pos),
x_(pos.x),
y_(pos.y),
flip_(SDL_FLIP_NONE) { SSprite::pos_ = pos; }
SMovingSprite::SMovingSprite(std::shared_ptr<Surface> surface)
: SSprite(surface),
x_(0.0f),
y_(0.0f),
flip_(SDL_FLIP_NONE) { SSprite::clear(); }
// Reinicia todas las variables
void SMovingSprite::clear() {
x_ = 0.0f; // Posición en el eje X
y_ = 0.0f; // Posición en el eje Y
vx_ = 0.0f; // Velocidad en el eje X. Cantidad de pixeles a desplazarse
vy_ = 0.0f; // Velocidad en el eje Y. Cantidad de pixeles a desplazarse
ax_ = 0.0f; // Aceleración en el eje X. Variación de la velocidad
ay_ = 0.0f; // Aceleración en el eje Y. Variación de la velocidad
flip_ = SDL_FLIP_NONE; // Establece como se ha de voltear el sprite
SSprite::clear();
}
// Mueve el sprite
void SMovingSprite::move() {
x_ += vx_;
y_ += vy_;
vx_ += ax_;
vy_ += ay_;
pos_.x = static_cast<int>(x_);
pos_.y = static_cast<int>(y_);
}
// Actualiza las variables internas del objeto
void SMovingSprite::update() {
move();
}
// Muestra el sprite por pantalla
void SMovingSprite::render() {
surface_->render(pos_.x, pos_.y, &clip_, flip_);
}
// Muestra el sprite por pantalla
void SMovingSprite::render(Uint8 source_color, Uint8 target_color) {
surface_->renderWithColorReplace(pos_.x, pos_.y, source_color, target_color, &clip_, flip_);
}
// Establece la posición y_ el tamaño del objeto
void SMovingSprite::setPos(SDL_FRect rect) {
x_ = static_cast<float>(rect.x);
y_ = static_cast<float>(rect.y);
pos_ = rect;
}
// Establece el valor de las variables
void SMovingSprite::setPos(float x, float y) {
x_ = x;
y_ = y;
pos_.x = static_cast<int>(x_);
pos_.y = static_cast<int>(y_);
}
// Establece el valor de la variable
void SMovingSprite::setPosX(float value) {
x_ = value;
pos_.x = static_cast<int>(x_);
}
// Establece el valor de la variable
void SMovingSprite::setPosY(float value) {
y_ = value;
pos_.y = static_cast<int>(y_);
}

View File

@@ -0,0 +1,81 @@
#pragma once
#include <SDL3/SDL.h>
#include <memory> // Para shared_ptr
#include "core/rendering/surface_sprite.hpp" // Para SSprite
class Surface; // lines 8-8
// Clase SMovingSprite. Añade movimiento y flip al sprite
class SMovingSprite : public SSprite {
public:
protected:
float x_; // Posición en el eje X
float y_; // Posición en el eje Y
float vx_ = 0.0f; // Velocidad en el eje X. Cantidad de pixeles a desplazarse
float vy_ = 0.0f; // Velocidad en el eje Y. Cantidad de pixeles a desplazarse
float ax_ = 0.0f; // Aceleración en el eje X. Variación de la velocidad
float ay_ = 0.0f; // Aceleración en el eje Y. Variación de la velocidad
SDL_FlipMode flip_; // Indica como se voltea el sprite
// Mueve el sprite
void move();
public:
// Constructor
SMovingSprite(std::shared_ptr<Surface> surface, SDL_FRect pos, SDL_FlipMode flip);
SMovingSprite(std::shared_ptr<Surface> surface, SDL_FRect pos);
explicit SMovingSprite(std::shared_ptr<Surface> surface);
// Destructor
virtual ~SMovingSprite() override = default;
// Actualiza las variables internas del objeto
virtual void update();
// Reinicia todas las variables a cero
void clear() override;
// Muestra el sprite por pantalla
void render() override;
void render(Uint8 source_color, Uint8 target_color) override;
// Obtiene la variable
float getPosX() const { return x_; }
float getPosY() const { return y_; }
float getVelX() const { return vx_; }
float getVelY() const { return vy_; }
float getAccelX() const { return ax_; }
float getAccelY() const { return ay_; }
// Establece la variable
void setVelX(float value) { vx_ = value; }
void setVelY(float value) { vy_ = value; }
void setAccelX(float value) { ax_ = value; }
void setAccelY(float value) { ay_ = value; }
// Establece el valor de la variable
void setFlip(SDL_FlipMode flip) { flip_ = flip; }
// Gira el sprite horizontalmente
void flip() { flip_ = (flip_ == SDL_FLIP_HORIZONTAL) ? SDL_FLIP_NONE : SDL_FLIP_HORIZONTAL; }
// Obtiene el valor de la variable
SDL_FlipMode getFlip() { return flip_; }
// Establece la posición y_ el tamaño del objeto
void setPos(SDL_FRect rect);
// Establece el valor de las variables
void setPos(float x, float y);
// Establece el valor de la variable
void setPosX(float value);
// Establece el valor de la variable
void setPosY(float value);
};

View File

@@ -0,0 +1,46 @@
#include "core/rendering/surface_sprite.hpp"
#include "core/rendering/surface.hpp" // Para Surface
// Constructor
SSprite::SSprite(std::shared_ptr<Surface> surface, float x, float y, float w, float h)
: surface_(surface),
pos_((SDL_FRect){x, y, w, h}),
clip_((SDL_FRect){0.0F, 0.0F, pos_.w, pos_.h}) {}
SSprite::SSprite(std::shared_ptr<Surface> surface, SDL_FRect rect)
: surface_(surface),
pos_(rect),
clip_((SDL_FRect){0, 0, pos_.w, pos_.h}) {}
SSprite::SSprite(std::shared_ptr<Surface> surface)
: surface_(surface),
pos_((SDL_FRect){0.0F, 0.0F, surface_->getWidth(), surface_->getHeight()}),
clip_(pos_) {}
// Muestra el sprite por pantalla
void SSprite::render() {
surface_->render(pos_.x, pos_.y, &clip_);
}
void SSprite::render(Uint8 source_color, Uint8 target_color) {
surface_->renderWithColorReplace(pos_.x, pos_.y, source_color, target_color, &clip_);
}
// Establece la posición del objeto
void SSprite::setPosition(float x, float y) {
pos_.x = x;
pos_.y = y;
}
// Establece la posición del objeto
void SSprite::setPosition(SDL_FPoint p) {
pos_.x = p.x;
pos_.y = p.y;
}
// Reinicia las variables a cero
void SSprite::clear() {
pos_ = {0, 0, 0, 0};
clip_ = {0, 0, 0, 0};
}

View File

@@ -0,0 +1,69 @@
#pragma once
#include <SDL3/SDL.h>
#include <memory> // Para shared_ptr
class Surface; // lines 5-5
// Clase SSprite
class SSprite {
protected:
// Variables
std::shared_ptr<Surface> surface_; // Surface donde estan todos los dibujos del sprite
SDL_FRect pos_; // Posición y tamaño donde dibujar el sprite
SDL_FRect clip_; // Rectangulo de origen de la surface que se dibujará en pantalla
public:
// Constructor
SSprite(std::shared_ptr<Surface>, float x, float y, float w, float h);
SSprite(std::shared_ptr<Surface>, SDL_FRect rect);
explicit SSprite(std::shared_ptr<Surface>);
// Destructor
virtual ~SSprite() = default;
// Muestra el sprite por pantalla
virtual void render();
virtual void render(Uint8 source_color, Uint8 target_color);
// Reinicia las variables a cero
virtual void clear();
// Obtiene la posición y el tamaño
float getX() const { return pos_.x; }
float getY() const { return pos_.y; }
float getWidth() const { return pos_.w; }
float getHeight() const { return pos_.h; }
// Devuelve el rectangulo donde está el sprite
SDL_FRect getPosition() const { return pos_; }
SDL_FRect& getRect() { return pos_; }
// Establece la posición y el tamaño
void setX(float x) { pos_.x = x; }
void setY(float y) { pos_.y = y; }
void setWidth(float w) { pos_.w = w; }
void setHeight(float h) { pos_.h = h; }
// Establece la posición del objeto
void setPosition(float x, float y);
void setPosition(SDL_FPoint p);
void setPosition(SDL_FRect r) { pos_ = r; }
// Aumenta o disminuye la posición
void incX(float value) { pos_.x += value; }
void incY(float value) { pos_.y += value; }
// Obtiene el rectangulo que se dibuja de la surface
SDL_FRect getClip() const { return clip_; }
// Establece el rectangulo que se dibuja de la surface
void setClip(SDL_FRect rect) { clip_ = rect; }
void setClip(float x, float y, float w, float h) { clip_ = (SDL_FRect){x, y, w, h}; }
// Obtiene un puntero a la surface
std::shared_ptr<Surface> getSurface() const { return surface_; }
// Establece la surface a utilizar
void setSurface(std::shared_ptr<Surface> surface) { surface_ = surface; }
};

View File

@@ -0,0 +1,241 @@
#include "core/rendering/text.hpp"
#include <SDL3/SDL.h>
#include <stddef.h> // Para size_t
#include <fstream> // Para basic_ifstream, basic_istream, basic_ostream
#include <iostream> // Para cerr
#include <stdexcept> // Para runtime_error
#include "core/rendering/screen.hpp" // Para Screen
#include "core/rendering/surface_sprite.hpp" // Para SSprite
#include "core/rendering/surface.hpp" // Para Surface
#include "utils/utils.hpp" // Para getFileName, stringToColor, printWithDots
// Llena una estructuta TextFile desde un fichero
std::shared_ptr<TextFile> loadTextFile(const std::string& file_path) {
auto tf = std::make_shared<TextFile>();
// Inicializa a cero el vector con las coordenadas
for (int i = 0; i < 128; ++i) {
tf->offset[i].x = 0;
tf->offset[i].y = 0;
tf->offset[i].w = 0;
tf->box_width = 0;
tf->box_height = 0;
}
// Abre el fichero para leer los valores
std::ifstream file(file_path);
if (file.is_open() && file.good()) {
std::string buffer;
// Lee los dos primeros valores del fichero
std::getline(file, buffer);
std::getline(file, buffer);
tf->box_width = std::stoi(buffer);
std::getline(file, buffer);
std::getline(file, buffer);
tf->box_height = std::stoi(buffer);
// lee el resto de datos del fichero
auto index = 32;
auto line_read = 0;
while (std::getline(file, buffer)) {
// Almacena solo las lineas impares
if (line_read % 2 == 1)
tf->offset[index++].w = std::stoi(buffer);
// Limpia el buffer
buffer.clear();
line_read++;
};
// Cierra el fichero
printWithDots("Text File : ", getFileName(file_path), "[ LOADED ]");
file.close();
}
// El fichero no se puede abrir
else {
std::cerr << "Error: Fichero no encontrado " << getFileName(file_path) << std::endl;
throw std::runtime_error("Fichero no encontrado: " + getFileName(file_path));
}
// Establece las coordenadas para cada caracter ascii de la cadena y su ancho
for (int i = 32; i < 128; ++i) {
tf->offset[i].x = ((i - 32) % 15) * tf->box_width;
tf->offset[i].y = ((i - 32) / 15) * tf->box_height;
}
return tf;
}
// Constructor
Text::Text(std::shared_ptr<Surface> surface, const std::string& text_file) {
// Carga los offsets desde el fichero
auto tf = loadTextFile(text_file);
// Inicializa variables desde la estructura
box_height_ = tf->box_height;
box_width_ = tf->box_width;
for (int i = 0; i < 128; ++i) {
offset_[i].x = tf->offset[i].x;
offset_[i].y = tf->offset[i].y;
offset_[i].w = tf->offset[i].w;
}
// Crea los objetos
sprite_ = std::make_unique<SSprite>(surface, (SDL_FRect){0.0F, 0.0F, static_cast<float>(box_width_), static_cast<float>(box_height_)});
// Inicializa variables
fixed_width_ = false;
}
// Constructor
Text::Text(std::shared_ptr<Surface> surface, std::shared_ptr<TextFile> text_file) {
// Inicializa variables desde la estructura
box_height_ = text_file->box_height;
box_width_ = text_file->box_width;
for (int i = 0; i < 128; ++i) {
offset_[i].x = text_file->offset[i].x;
offset_[i].y = text_file->offset[i].y;
offset_[i].w = text_file->offset[i].w;
}
// Crea los objetos
sprite_ = std::make_unique<SSprite>(surface, (SDL_FRect){0.0F, 0.0F, static_cast<float>(box_width_), static_cast<float>(box_height_)});
// Inicializa variables
fixed_width_ = false;
}
// Escribe texto en pantalla
void Text::write(int x, int y, const std::string& text, int kerning, int lenght) {
int shift = 0;
if (lenght == -1)
lenght = text.length();
sprite_->setY(y);
for (int i = 0; i < lenght; ++i) {
auto index = static_cast<int>(text[i]);
sprite_->setClip(offset_[index].x, offset_[index].y, box_width_, box_height_);
sprite_->setX(x + shift);
sprite_->render(1, 15);
shift += offset_[static_cast<int>(text[i])].w + kerning;
}
}
// Escribe el texto en una surface
std::shared_ptr<Surface> Text::writeToSurface(const std::string& text, int zoom, int kerning) {
auto width = lenght(text, kerning) * zoom;
auto height = box_height_ * zoom;
auto surface = std::make_shared<Surface>(width, height);
auto previuos_renderer = Screen::get()->getRendererSurface();
Screen::get()->setRendererSurface(surface);
surface->clear(stringToColor("transparent"));
write(0, 0, text, kerning);
Screen::get()->setRendererSurface(previuos_renderer);
return surface;
}
// Escribe el texto con extras en una surface
std::shared_ptr<Surface> Text::writeDXToSurface(Uint8 flags, const std::string& text, int kerning, Uint8 textColor, Uint8 shadow_distance, Uint8 shadow_color, int lenght) {
auto width = Text::lenght(text, kerning) + shadow_distance;
auto height = box_height_ + shadow_distance;
auto surface = std::make_shared<Surface>(width, height);
auto previuos_renderer = Screen::get()->getRendererSurface();
Screen::get()->setRendererSurface(surface);
surface->clear(stringToColor("transparent"));
writeDX(flags, 0, 0, text, kerning, textColor, shadow_distance, shadow_color, lenght);
Screen::get()->setRendererSurface(previuos_renderer);
return surface;
}
// Escribe el texto con colores
void Text::writeColored(int x, int y, const std::string& text, Uint8 color, int kerning, int lenght) {
int shift = 0;
if (lenght == -1) {
lenght = text.length();
}
sprite_->setY(y);
for (int i = 0; i < lenght; ++i) {
auto index = static_cast<int>(text[i]);
sprite_->setClip(offset_[index].x, offset_[index].y, box_width_, box_height_);
sprite_->setX(x + shift);
sprite_->render(1, color);
shift += offset_[static_cast<int>(text[i])].w + kerning;
}
}
// Escribe el texto con sombra
void Text::writeShadowed(int x, int y, const std::string& text, Uint8 color, Uint8 shadow_distance, int kerning, int lenght) {
writeColored(x + shadow_distance, y + shadow_distance, text, color, kerning, lenght);
write(x, y, text, kerning, lenght);
}
// Escribe el texto centrado en un punto x
void Text::writeCentered(int x, int y, const std::string& text, int kerning, int lenght) {
x -= (Text::lenght(text, kerning) / 2);
write(x, y, text, kerning, lenght);
}
// Escribe texto con extras
void Text::writeDX(Uint8 flags, int x, int y, const std::string& text, int kerning, Uint8 textColor, Uint8 shadow_distance, Uint8 shadow_color, int lenght) {
const auto centered = ((flags & TEXT_CENTER) == TEXT_CENTER);
const auto shadowed = ((flags & TEXT_SHADOW) == TEXT_SHADOW);
const auto colored = ((flags & TEXT_COLOR) == TEXT_COLOR);
const auto stroked = ((flags & TEXT_STROKE) == TEXT_STROKE);
if (centered) {
x -= (Text::lenght(text, kerning) / 2);
}
if (shadowed) {
writeColored(x + shadow_distance, y + shadow_distance, text, shadow_color, kerning, lenght);
}
if (stroked) {
for (int dist = 1; dist <= shadow_distance; ++dist) {
for (int dy = -dist; dy <= dist; ++dy) {
for (int dx = -dist; dx <= dist; ++dx) {
writeColored(x + dx, y + dy, text, shadow_color, kerning, lenght);
}
}
}
}
if (colored) {
writeColored(x, y, text, textColor, kerning, lenght);
} else {
writeColored(x, y, text, textColor, kerning, lenght);
// write(x, y, text, kerning, lenght);
}
}
// Obtiene la longitud en pixels de una cadena
int Text::lenght(const std::string& text, int kerning) const {
int shift = 0;
for (size_t i = 0; i < text.length(); ++i)
shift += (offset_[static_cast<int>(text[i])].w + kerning);
// Descuenta el kerning del último caracter
return shift - kerning;
}
// Devuelve el valor de la variable
int Text::getCharacterSize() const {
return box_width_;
}
// Establece si se usa un tamaño fijo de letra
void Text::setFixedWidth(bool value) {
fixed_width_ = value;
}

View File

@@ -0,0 +1,78 @@
#pragma once
#include <SDL3/SDL.h>
#include <memory> // Para shared_ptr, unique_ptr
#include <string> // Para string
#include "core/rendering/surface_sprite.hpp" // Para SSprite
class Surface; // lines 8-8
constexpr int TEXT_COLOR = 1;
constexpr int TEXT_SHADOW = 2;
constexpr int TEXT_CENTER = 4;
constexpr int TEXT_STROKE = 8;
struct TextOffset {
int x, y, w;
};
struct TextFile {
int box_width; // Anchura de la caja de cada caracter en el png
int box_height; // Altura de la caja de cada caracter en el png
TextOffset offset[128]; // Vector con las posiciones y ancho de cada letra
};
// Llena una estructuta TextFile desde un fichero
std::shared_ptr<TextFile> loadTextFile(const std::string& file_path);
// Clase texto. Pinta texto en pantalla a partir de un bitmap
class Text {
private:
// Objetos y punteros
std::unique_ptr<SSprite> sprite_ = nullptr; // Objeto con los graficos para el texto
// Variables
int box_width_ = 0; // Anchura de la caja de cada caracter en el png
int box_height_ = 0; // Altura de la caja de cada caracter en el png
bool fixed_width_ = false; // Indica si el texto se ha de escribir con longitud fija en todas las letras
TextOffset offset_[128] = {}; // Vector con las posiciones y ancho de cada letra
public:
// Constructor
Text(std::shared_ptr<Surface> surface, const std::string& text_file);
Text(std::shared_ptr<Surface> surface, std::shared_ptr<TextFile> text_file);
// Destructor
~Text() = default;
// Escribe el texto en pantalla
void write(int x, int y, const std::string& text, int kerning = 1, int lenght = -1);
// Escribe el texto en una textura
std::shared_ptr<Surface> writeToSurface(const std::string& text, int zoom = 1, int kerning = 1);
// Escribe el texto con extras en una textura
std::shared_ptr<Surface> writeDXToSurface(Uint8 flags, const std::string& text, int kerning = 1, Uint8 textColor = Uint8(), Uint8 shadow_distance = 1, Uint8 shadow_color = Uint8(), int lenght = -1);
// Escribe el texto con colores
void writeColored(int x, int y, const std::string& text, Uint8 color, int kerning = 1, int lenght = -1);
// Escribe el texto con sombra
void writeShadowed(int x, int y, const std::string& text, Uint8 color, Uint8 shadow_distance = 1, int kerning = 1, int lenght = -1);
// Escribe el texto centrado en un punto x
void writeCentered(int x, int y, const std::string& text, int kerning = 1, int lenght = -1);
// Escribe texto con extras
void writeDX(Uint8 flags, int x, int y, const std::string& text, int kerning = 1, Uint8 textColor = Uint8(), Uint8 shadow_distance = 1, Uint8 shadow_color = Uint8(), int lenght = -1);
// Obtiene la longitud en pixels de una cadena
int lenght(const std::string& text, int kerning = 1) const;
// Devuelve el valor de la variable
int getCharacterSize() const;
// Establece si se usa un tamaño fijo de letra
void setFixedWidth(bool value);
};

View File

@@ -0,0 +1,167 @@
#include "core/rendering/texture.hpp"
#include <SDL3/SDL.h>
#include <iostream> // Para basic_ostream, operator<<, endl, cout
#include <stdexcept> // Para runtime_error
#include <string> // Para char_traits, operator<<, string, opera...
#include <vector> // Para vector
#include "utils/utils.hpp" // Para getFileName, Color, printWithDots
#define STB_IMAGE_IMPLEMENTATION
#include "external/stb_image.h" // para stbi_failure_reason, stbi_image_free
// Constructor
Texture::Texture(SDL_Renderer* renderer, const std::string& path)
: renderer_(renderer),
path_(path) {
// Carga el fichero en la textura
if (!path_.empty()) {
// Obtiene la extensión
const std::string extension = path_.substr(path_.find_last_of(".") + 1);
// .png
if (extension == "png") {
loadFromFile(path_);
}
}
}
// Destructor
Texture::~Texture() {
unloadTexture();
palettes_.clear();
}
// Carga una imagen desde un fichero
bool Texture::loadFromFile(const std::string& file_path) {
if (file_path.empty()) {
return false;
}
int req_format = STBI_rgb_alpha;
int width, height, orig_format;
unsigned char* data = stbi_load(file_path.c_str(), &width, &height, &orig_format, req_format);
if (!data) {
std::cerr << "Error: Fichero no encontrado " << getFileName(file_path) << std::endl;
throw std::runtime_error("Fichero no encontrado: " + getFileName(file_path));
} else {
printWithDots("Image : ", getFileName(file_path), "[ LOADED ]");
}
int pitch;
SDL_PixelFormat pixel_format;
// STBI_rgb_alpha (RGBA)
pitch = 4 * width;
pixel_format = SDL_PIXELFORMAT_RGBA32;
// Limpia
unloadTexture();
// La textura final
SDL_Texture* newTexture = nullptr;
// Carga la imagen desde una ruta específica
auto *loaded_surface = SDL_CreateSurfaceFrom(width, height, pixel_format, static_cast<void *>(data), pitch);
if (loaded_surface == nullptr) {
std::cout << "Unable to load image " << file_path << std::endl;
} else {
// Crea la textura desde los pixels de la surface
newTexture = SDL_CreateTextureFromSurface(renderer_, loaded_surface);
if (newTexture == nullptr) {
std::cout << "Unable to create texture from " << file_path << "! SDL Error: " << SDL_GetError() << std::endl;
} else {
// Obtiene las dimensiones de la imagen
width_ = loaded_surface->w;
height_ = loaded_surface->h;
}
// Elimina la textura cargada
SDL_DestroySurface(loaded_surface);
}
// Return success
stbi_image_free(data);
texture_ = newTexture;
return texture_ != nullptr;
}
// Crea una textura en blanco
auto Texture::createBlank(int width, int height, SDL_PixelFormat format, SDL_TextureAccess access) -> bool {
// Crea una textura sin inicializar
texture_ = SDL_CreateTexture(renderer_, format, access, width, height);
if (texture_ == nullptr) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unable to create blank texture! SDL Error: %s", SDL_GetError());
} else {
width_ = width;
height_ = height;
}
return texture_ != nullptr;
}
// Libera la memoria de la textura
void Texture::unloadTexture() {
// Libera la textura
if (texture_) {
SDL_DestroyTexture(texture_);
texture_ = nullptr;
width_ = 0;
height_ = 0;
}
}
// Establece el color para la modulacion
void Texture::setColor(Uint8 red, Uint8 green, Uint8 blue) { SDL_SetTextureColorMod(texture_, red, green, blue); }
void Texture::setColor(Color color) { SDL_SetTextureColorMod(texture_, color.r, color.g, color.b); }
// Establece el blending
void Texture::setBlendMode(SDL_BlendMode blending) { SDL_SetTextureBlendMode(texture_, blending); }
// Establece el alpha para la modulación
void Texture::setAlpha(Uint8 alpha) { SDL_SetTextureAlphaMod(texture_, alpha); }
// Renderiza la textura en un punto específico
void Texture::render(float x, float y, SDL_FRect* clip, float zoomW, float zoomH, double angle, SDL_FPoint* center, SDL_FlipMode flip) {
// Establece el destino de renderizado en la pantalla
SDL_FRect render_quad = {x, y, width_, height_};
// Obtiene las dimesiones del clip de renderizado
if (clip != nullptr) {
render_quad.w = clip->w;
render_quad.h = clip->h;
}
// Calcula el zoom y las coordenadas
if (zoomH != 1.0f || zoomW != 1.0f) {
render_quad.x = render_quad.x + (render_quad.w / 2);
render_quad.y = render_quad.y + (render_quad.h / 2);
render_quad.w = render_quad.w * zoomW;
render_quad.h = render_quad.h * zoomH;
render_quad.x = render_quad.x - (render_quad.w / 2);
render_quad.y = render_quad.y - (render_quad.h / 2);
}
// Renderiza a pantalla
SDL_RenderTextureRotated(renderer_, texture_, clip, &render_quad, angle, center, flip);
}
// Establece la textura como objetivo de renderizado
void Texture::setAsRenderTarget(SDL_Renderer* renderer) { SDL_SetRenderTarget(renderer, texture_); }
// Obtiene el ancho de la imagen
int Texture::getWidth() { return width_; }
// Obtiene el alto de la imagen
int Texture::getHeight() { return height_; }
// Recarga la textura
bool Texture::reLoad() { return loadFromFile(path_); }
// Obtiene la textura
SDL_Texture* Texture::getSDLTexture() { return texture_; }
// Obtiene el renderizador
SDL_Renderer* Texture::getRenderer() { return renderer_; }

View File

@@ -0,0 +1,67 @@
#pragma once
#include <SDL3/SDL.h>
#include <string> // Para string
#include <vector> // Para vector
struct Color; // lines 11-11
class Texture {
private:
// Objetos y punteros
SDL_Renderer* renderer_; // Renderizador donde dibujar la textura
SDL_Texture* texture_ = nullptr; // La textura
// Variables
std::string path_; // Ruta de la imagen de la textura
float width_ = 0.0F; // Ancho de la imagen
float height_ = 0.0F; // Alto de la imagen
std::vector<std::vector<Uint32>> palettes_; // Vector con las diferentes paletas
// Libera la memoria de la textura
void unloadTexture();
public:
// Constructor
explicit Texture(SDL_Renderer* renderer, const std::string& path = std::string());
// Destructor
~Texture();
// Carga una imagen desde un fichero
bool loadFromFile(const std::string& path);
// Crea una textura en blanco
auto createBlank(int width, int height, SDL_PixelFormat format = SDL_PIXELFORMAT_RGBA8888, SDL_TextureAccess access = SDL_TEXTUREACCESS_STREAMING) -> bool;
// Establece el color para la modulacion
void setColor(Uint8 red, Uint8 green, Uint8 blue);
void setColor(Color color);
// Establece el blending
void setBlendMode(SDL_BlendMode blending);
// Establece el alpha para la modulación
void setAlpha(Uint8 alpha);
// Renderiza la textura en un punto específico
void render(float x, float y, SDL_FRect* clip = nullptr, float zoomW = 1, float zoomH = 1, double angle = 0.0, SDL_FPoint* center = nullptr, SDL_FlipMode flip = SDL_FLIP_NONE);
// Establece la textura como objetivo de renderizado
void setAsRenderTarget(SDL_Renderer* renderer);
// Obtiene el ancho de la imagen
int getWidth();
// Obtiene el alto de la imagen
int getHeight();
// Recarga la textura
bool reLoad();
// Obtiene la textura
SDL_Texture* getSDLTexture();
// Obtiene el renderizador
SDL_Renderer* getRenderer();
};