#define STB_IMAGE_IMPLEMENTATION
+#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include <stb/stb_image.h>
+#include <stb/stb_image_resize.h>
#include <stdbool.h>
-#include <dragonstd/list.h>
+#include <dragonstd/tree.h>
#include "client/client_config.h"
+#include "client/gl_debug.h"
#include "client/texture.h"
-#include "util.h"
-static List textures;
+static Tree textures;
-__attribute((constructor(101))) static void textures_init()
+typedef struct {
+ char *path;
+ Texture texture;
+} TextureLookup;
+
+static int cmp_texture(TextureLookup *texture, char *path)
{
- textures = list_create(&list_compare_string);
+ return strcmp(texture->path, path);
}
-static void list_delete_texture(unused void *key, void *value, unused void *arg)
+static bool lookup_texture(char *path, Texture **texture)
{
- texture_delete(value);
+ TreeNode **node = tree_nfd(&textures, path, &cmp_texture);
+
+ if (*node) {
+ *texture = &((TextureLookup *) (*node)->dat)->texture;
+ return true;
+ }
+
+ TextureLookup *lookup = malloc(sizeof *lookup);
+ lookup->path = strdup(path);
+ *texture = &lookup->texture;
+
+ tree_nmk(&textures, node, lookup);
+ return false;
}
-__attribute((destructor)) static void textures_deinit()
+static void delete_texture(TextureLookup *lookup)
{
- list_clear_func(&textures, &list_delete_texture, NULL);
+ free(lookup->path);
+ texture_destroy(&lookup->texture);
+ free(lookup);
}
-Texture *texture_create(unsigned char *data, int width, int height, GLenum format, bool mipmap)
+__attribute__((constructor(101))) static void textures_init()
{
- Texture *texture = malloc(sizeof(Texture));
- texture->width = width;
- texture->height = height;
+ tree_ini(&textures);
+}
- glGenTextures(1, &texture->id);
+__attribute__((destructor)) static void textures_deinit()
+{
+ tree_clr(&textures, &delete_texture, NULL, NULL, 0);
+}
- glBindTexture(GL_TEXTURE_2D, texture->id);
+Texture *texture_load(char *path, bool mipmap)
+{
+ Texture *texture;
+ if (lookup_texture(path, &texture))
+ return texture;
+
+ unsigned char *data = stbi_load(path,
+ &texture->width, &texture->height, &texture->channels, 0);
+ if (!data) {
+ fprintf(stderr, "[error] failed to load texture %s\n", path);
+ exit(EXIT_FAILURE);
+ }
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (mipmap && client_config.mipmap) ? GL_NEAREST_MIPMAP_NEAREST : GL_NEAREST);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+ texture_upload(texture, data, GL_RGBA, mipmap);
+ stbi_image_free(data);
- glTexImage2D(GL_TEXTURE_2D, 0, format, texture->width, texture->height, 0, format, GL_UNSIGNED_BYTE, data);
- glGenerateMipmap(GL_TEXTURE_2D);
+ return texture;
+}
- glBindTexture(GL_TEXTURE_2D, 0);
+static inline int least_common_multiple(int a, int b)
+{
+ int high, low;
+ if (a > b) {
+ high = a;
+ low = b;
+ } else {
+ high = b;
+ low = a;
+ }
- return texture;
+ int lcm = high;
+ while (lcm % low)
+ lcm += high;
+ return lcm;
}
-GLuint texture_create_cubemap(char *path)
+Texture *texture_load_cubemap(char *path)
{
- GLuint id;
- glGenTextures(1, &id);
+ Texture *texture;
+ if (lookup_texture(path, &texture))
+ return texture;
- glBindTexture(GL_TEXTURE_CUBE_MAP, id);
+ glGenTextures(1, &texture->txo); GL_DEBUG
+ glBindTexture(GL_TEXTURE_CUBE_MAP, texture->txo); GL_DEBUG
const char *directions[6] = {
"right",
"back",
};
+ typedef struct {
+ unsigned char *data;
+ int width, height, channels;
+ } CubemapFace;
+
+ CubemapFace faces[6];
+ int size = 1;
+
for (int i = 0; i < 6; i++) {
char filename[strlen(path) + 1 + strlen(directions[i]) + 1 + 3 + 1];
sprintf(filename, "%s/%s.png", path, directions[i]);
- int width, height, channels;
- unsigned char *data = stbi_load(filename, &width, &height, &channels, 0);
- if (! data) {
- fprintf(stderr, "Failed to load texture %s\n", filename);
- return 0;
+ if (!(faces[i].data = stbi_load(filename,
+ &faces[i].width, &faces[i].height, &faces[i].channels, 0))) {
+ fprintf(stderr, "[error] failed to load texture %s\n", filename);
+ exit(EXIT_FAILURE);
}
- glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
- stbi_image_free(data);
+ size = least_common_multiple(size, faces[i].width);
+ size = least_common_multiple(size, faces[i].height);
}
- glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
- glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
-
- return id;
-}
-
-void texture_delete(Texture *texture)
-{
- glDeleteTextures(1, &texture->id);
- free(texture);
-}
+ for (int i = 0; i < 6; i++) {
+ unsigned char *data = faces[i].data;
+
+ bool resize = faces[i].width != size || faces[i].height != size;
+ if (resize) {
+ data = malloc(size * size * faces[i].channels);
+
+ stbir_resize_uint8_generic(
+ faces[i].data, faces[i].width, faces[i].height, 0,
+ data, size, size, 0,
+ faces[i].channels, STBIR_ALPHA_CHANNEL_NONE, 0,
+ STBIR_EDGE_CLAMP, STBIR_FILTER_BOX, STBIR_COLORSPACE_LINEAR,
+ NULL);
+ }
-Texture *texture_load(char *path, bool mipmap)
-{
- int width, height, channels;
+ glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGBA,
+ size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); GL_DEBUG
- unsigned char *data = stbi_load(path, &width, &height, &channels, 0);
- if (! data) {
- fprintf(stderr, "Failed to load texture %s\n", path);
- return NULL;
+ stbi_image_free(faces[i].data);
+ if (resize)
+ stbi_image_free(data);
}
- Texture *texture = texture_create(data, width, height, GL_RGBA, mipmap);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST); GL_DEBUG
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST); GL_DEBUG
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); GL_DEBUG
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); GL_DEBUG
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); GL_DEBUG
- stbi_image_free(data);
+ return texture;
+}
- list_put(&textures, texture, NULL);
+void texture_upload(Texture *texture, unsigned char *data, GLenum format, bool mipmap)
+{
+ glGenTextures(1, &texture->txo); GL_DEBUG
+ glBindTexture(GL_TEXTURE_2D, texture->txo); GL_DEBUG
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (mipmap && client_config.mipmap)
+ ? GL_NEAREST_MIPMAP_NEAREST : GL_NEAREST); GL_DEBUG
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); GL_DEBUG
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); GL_DEBUG
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); GL_DEBUG
+
+ glTexImage2D(GL_TEXTURE_2D, 0, format,
+ texture->width, texture->height, 0, format, GL_UNSIGNED_BYTE, data); GL_DEBUG
+ glGenerateMipmap(GL_TEXTURE_2D); GL_DEBUG
+}
- return texture;
+void texture_destroy(Texture *texture)
+{
+ glDeleteTextures(1, &texture->txo); GL_DEBUG
}