Files
fn_registry/cpp/functions/gfx/gl_texture_load.cpp
T
egutierrez cb0591ff91 feat(gfx): añadir gl_texture_load_cpp_gfx
Funcion impura del registry que carga PNG/JPG/BMP/TGA/HDR a una textura
OpenGL lista para sampler2D. Composable con gl_loader, gl_shader,
shader_canvas. Genera mipmaps, soporta sRGB y HDR (RGBA16F).

API:
  GlTexture gl_texture_load(path, flip_y=true, srgb=false)
  GlTexture gl_texture_load_from_memory(data, size, ...)
  void      gl_texture_destroy(tex)
  const char* gl_texture_last_error()  // thread-local
  void      gl_texture_bind_uniform(prog, name, tex, unit)

Errores via thread_local string accesible por gl_texture_last_error().

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 20:58:14 +02:00

152 lines
4.7 KiB
C++

#include "gl_texture_load.h"
#include "../../vendor/stb/stb_image.h"
#include <cstring>
#include <string>
namespace fn {
namespace {
// Error thread-local — cada thread mantiene su propio mensaje.
thread_local std::string g_last_error;
bool ends_with_ci(const char* s, const char* suffix) {
if (!s || !suffix) return false;
size_t ls = std::strlen(s);
size_t lf = std::strlen(suffix);
if (lf > ls) return false;
const char* a = s + ls - lf;
for (size_t i = 0; i < lf; i++) {
char ca = a[i]; char cb = suffix[i];
if (ca >= 'A' && ca <= 'Z') ca = char(ca - 'A' + 'a');
if (cb >= 'A' && cb <= 'Z') cb = char(cb - 'A' + 'a');
if (ca != cb) return false;
}
return true;
}
GLuint upload_ldr(const unsigned char* pixels, int w, int h, bool srgb) {
GLuint id = 0;
glGenTextures(1, &id);
if (id == 0) return 0;
glBindTexture(GL_TEXTURE_2D, id);
GLint internal = srgb ? GL_SRGB8_ALPHA8 : GL_RGBA8;
glTexImage2D(GL_TEXTURE_2D, 0, internal, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glGenerateMipmap(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, 0);
return id;
}
GLuint upload_hdr(const float* pixels, int w, int h) {
GLuint id = 0;
glGenTextures(1, &id);
if (id == 0) return 0;
glBindTexture(GL_TEXTURE_2D, id);
// GL_RGBA16F + GL_FLOAT — GL 3.0+. En GL 2.1 no hay GL_RGBA16F; aceptamos
// la limitacion (driver moderno).
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, w, h, 0, GL_RGBA, GL_FLOAT, pixels);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glGenerateMipmap(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, 0);
return id;
}
void set_error(const char* msg) {
g_last_error = msg ? msg : "unknown error";
}
} // namespace
GlTexture gl_texture_load(const char* path, bool flip_y, bool srgb) {
GlTexture tex{};
if (!path || !*path) {
set_error("gl_texture_load: empty path");
return tex;
}
stbi_set_flip_vertically_on_load(flip_y ? 1 : 0);
if (ends_with_ci(path, ".hdr")) {
int w = 0, h = 0, ch = 0;
float* px = stbi_loadf(path, &w, &h, &ch, 4);
if (!px) {
set_error(stbi_failure_reason());
return tex;
}
tex.id = upload_hdr(px, w, h);
tex.w = w; tex.h = h; tex.channels = ch;
stbi_image_free(px);
if (tex.id == 0) set_error("glGenTextures returned 0");
else g_last_error.clear();
return tex;
}
int w = 0, h = 0, ch = 0;
unsigned char* px = stbi_load(path, &w, &h, &ch, 4); // forzamos RGBA
if (!px) {
set_error(stbi_failure_reason());
return tex;
}
tex.id = upload_ldr(px, w, h, srgb);
tex.w = w; tex.h = h; tex.channels = ch;
stbi_image_free(px);
if (tex.id == 0) set_error("glGenTextures returned 0");
else g_last_error.clear();
return tex;
}
GlTexture gl_texture_load_from_memory(const unsigned char* data, int size,
bool flip_y, bool srgb) {
GlTexture tex{};
if (!data || size <= 0) {
set_error("gl_texture_load_from_memory: empty buffer");
return tex;
}
stbi_set_flip_vertically_on_load(flip_y ? 1 : 0);
int w = 0, h = 0, ch = 0;
unsigned char* px = stbi_load_from_memory(data, size, &w, &h, &ch, 4);
if (!px) {
set_error(stbi_failure_reason());
return tex;
}
tex.id = upload_ldr(px, w, h, srgb);
tex.w = w; tex.h = h; tex.channels = ch;
stbi_image_free(px);
if (tex.id == 0) set_error("glGenTextures returned 0");
else g_last_error.clear();
return tex;
}
void gl_texture_destroy(GlTexture& tex) {
if (tex.id != 0) {
glDeleteTextures(1, &tex.id);
tex.id = 0;
}
tex.w = tex.h = tex.channels = 0;
}
const char* gl_texture_last_error() {
return g_last_error.c_str();
}
void gl_texture_bind_uniform(GLuint program, const char* name,
const GlTexture& tex, int unit) {
if (program == 0 || !name || !tex.ok()) return;
glActiveTexture(GL_TEXTURE0 + unit);
glBindTexture(GL_TEXTURE_2D, tex.id);
GLint loc = glGetUniformLocation(program, name);
if (loc >= 0) glUniform1i(loc, unit);
}
} // namespace fn