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>
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
#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
|
||||
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
// gl_texture_load — carga PNG/JPG/BMP/TGA/HDR desde disco o memoria a una
|
||||
// textura OpenGL lista para usar como sampler2D en shaders. Vendorea stb_image
|
||||
// en cpp/vendor/stb/. Funcion impura: hace I/O y crea recursos GPU.
|
||||
|
||||
#include "gl_loader.h"
|
||||
|
||||
namespace fn {
|
||||
|
||||
struct GlTexture {
|
||||
GLuint id = 0;
|
||||
int w = 0;
|
||||
int h = 0;
|
||||
int channels = 0;
|
||||
bool ok() const { return id != 0; }
|
||||
};
|
||||
|
||||
// Carga desde disco. flip_y=true (por defecto) coincide con la convencion
|
||||
// de OpenGL (V hacia arriba). srgb=true sube como GL_SRGB8_ALPHA8 (gamma
|
||||
// correcto sin pow(c, 2.2) en shader). Si el path termina en .hdr se carga
|
||||
// como float (GL_RGBA16F).
|
||||
//
|
||||
// Si falla, devuelve GlTexture con id=0 (ok() == false). Llama a
|
||||
// gl_texture_last_error() para obtener el detalle.
|
||||
GlTexture gl_texture_load(const char* path, bool flip_y = true, bool srgb = false);
|
||||
|
||||
// Igual que arriba pero leyendo de un buffer en memoria (PNG/JPG/etc bytes).
|
||||
GlTexture gl_texture_load_from_memory(const unsigned char* data, int size,
|
||||
bool flip_y = true, bool srgb = false);
|
||||
|
||||
// Libera la textura GPU y deja id=0.
|
||||
void gl_texture_destroy(GlTexture& tex);
|
||||
|
||||
// Mensaje de error del ultimo gl_texture_load* en este thread. "" si no hubo
|
||||
// fallo. Permanece valido hasta el siguiente gl_texture_load* en el thread.
|
||||
const char* gl_texture_last_error();
|
||||
|
||||
// Helper: bind a una texture unit y subir uniform sampler2D al programa.
|
||||
// program debe ser un GLuint valido, name el nombre del uniform sampler2D,
|
||||
// unit el slot de textura (0..N).
|
||||
void gl_texture_bind_uniform(GLuint program, const char* name,
|
||||
const GlTexture& tex, int unit);
|
||||
|
||||
} // namespace fn
|
||||
@@ -0,0 +1,108 @@
|
||||
---
|
||||
name: gl_texture_load
|
||||
kind: function
|
||||
lang: cpp
|
||||
domain: gfx
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "GlTexture gl_texture_load(const char* path, bool flip_y, bool srgb)"
|
||||
description: "Carga PNG/JPG/BMP/TGA/HDR desde disco (o memoria) a una textura OpenGL lista para usar como sampler2D. Vendorea stb_image. Soporta sRGB (GL_SRGB8_ALPHA8) y HDR (GL_RGBA16F via stbi_loadf). Genera mipmaps automaticamente. flip_y=true por defecto coincide con la convencion UV de OpenGL."
|
||||
tags: [opengl, texture, image, png, jpg, hdr, stb_image, gfx]
|
||||
uses_functions: [gl_loader_cpp_gfx]
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: [stb_image.h, GL/gl.h, GL/glext.h]
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "cpp/functions/gfx/gl_texture_load.cpp"
|
||||
framework: opengl
|
||||
params:
|
||||
- name: path
|
||||
desc: "Ruta al archivo de imagen (PNG/JPG/BMP/TGA o .hdr para float). UTF-8."
|
||||
- name: flip_y
|
||||
desc: "Si es true, voltea verticalmente al cargar (convencion OpenGL: V hacia arriba). Default true."
|
||||
- name: srgb
|
||||
desc: "Si es true, sube como GL_SRGB8_ALPHA8 (gamma correcto sin pow(c, 2.2) en shader). Solo aplica a LDR. Default false."
|
||||
output: "GlTexture con id=GLuint listo para glBindTexture, w/h/channels de la imagen original. Si falla, id=0 y ok()==false; usar gl_texture_last_error() para detalle."
|
||||
---
|
||||
|
||||
# gl_texture_load
|
||||
|
||||
Funcion impura del registry C++ que carga una imagen desde disco (o memoria) y devuelve una textura OpenGL lista para samplear desde un shader. Vendorea [stb_image](https://github.com/nothings/stb) en `cpp/vendor/stb/`.
|
||||
|
||||
## API
|
||||
|
||||
```cpp
|
||||
namespace fn {
|
||||
|
||||
struct GlTexture {
|
||||
GLuint id = 0;
|
||||
int w = 0;
|
||||
int h = 0;
|
||||
int channels = 0;
|
||||
bool ok() const { return id != 0; }
|
||||
};
|
||||
|
||||
GlTexture gl_texture_load(const char* path, bool flip_y = true, bool srgb = false);
|
||||
GlTexture gl_texture_load_from_memory(const unsigned char* data, int size,
|
||||
bool flip_y = true, bool srgb = false);
|
||||
|
||||
void gl_texture_destroy(GlTexture& tex);
|
||||
const char* gl_texture_last_error();
|
||||
void gl_texture_bind_uniform(GLuint program, const char* name,
|
||||
const GlTexture& tex, int unit);
|
||||
|
||||
} // namespace fn
|
||||
```
|
||||
|
||||
## Uso
|
||||
|
||||
```cpp
|
||||
#include "gfx/gl_texture_load.h"
|
||||
|
||||
auto tex = fn::gl_texture_load("assets/noise.png");
|
||||
if (!tex.ok()) {
|
||||
std::fprintf(stderr, "error: %s\n", fn::gl_texture_last_error());
|
||||
return 1;
|
||||
}
|
||||
|
||||
glUseProgram(prog);
|
||||
fn::gl_texture_bind_uniform(prog, "u_noise", tex, /*unit=*/0);
|
||||
glDrawArrays(GL_TRIANGLES, 0, 6);
|
||||
|
||||
// al destruir:
|
||||
fn::gl_texture_destroy(tex);
|
||||
```
|
||||
|
||||
## Comportamiento
|
||||
|
||||
- Forzamos 4 canales (RGBA) al decodificar via stb (`req_comp=4`), asi el upload a `glTexImage2D` siempre es regular.
|
||||
- Filtros: `GL_LINEAR_MIPMAP_LINEAR` (min) + `GL_LINEAR` (mag). Mipmaps generados con `glGenerateMipmap`.
|
||||
- Wrap: `GL_REPEAT` en S y T.
|
||||
- HDR (`.hdr`): `stbi_loadf` + `GL_RGBA16F` (driver con GL 3.0+). En este caso `srgb` se ignora.
|
||||
- sRGB (LDR + `srgb=true`): internal format `GL_SRGB8_ALPHA8`. El driver convierte a linear automaticamente al samplear.
|
||||
- `flip_y=true` por defecto — la mayoria de PNGs vienen con (0,0) arriba; OpenGL espera (0,0) abajo.
|
||||
|
||||
## Errores
|
||||
|
||||
- En fallo, `id == 0` y `ok() == false`. El detalle (string de stb o "glGenTextures returned 0") se guarda en un `thread_local` accesible via `gl_texture_last_error()` hasta el siguiente `gl_texture_load*` en el mismo thread.
|
||||
|
||||
## Limites
|
||||
|
||||
- Tamano practico: <= 8192 px por lado (depende del driver — `GL_MAX_TEXTURE_SIZE`).
|
||||
- No reentrante con la misma stb-flag global: stb usa una variable global para `flip_vertically`. Si dos threads cargan en paralelo, el resultado puede mezclar los flags. Para paralelo serio, serializar las llamadas o usar `stbi__ldr_to_hdr` directo con flag local.
|
||||
|
||||
## Composicion
|
||||
|
||||
Depende de `gl_loader_cpp_gfx` para los simbolos `glActiveTexture`, `glGenerateMipmap`, `glUseProgram`, `glGetUniformLocation`, `glUniform1i` (en Linux son simbolos directos via `GL_GLEXT_PROTOTYPES`; en Windows se resuelven con `wglGetProcAddress`).
|
||||
|
||||
Compone naturalmente con:
|
||||
- `gl_shader_cpp_gfx` — para usar la textura en un fragment shader (uniform sampler2D).
|
||||
- `shader_canvas_cpp_gfx` — un canvas full-screen que samplea la textura con uniforms de tint/zoom.
|
||||
|
||||
## Atribucion
|
||||
|
||||
stb_image v2.30 — public domain (MIT-0). Ver `cpp/vendor/stb/README.md`.
|
||||
Reference in New Issue
Block a user