aboutsummaryrefslogtreecommitdiff
path: root/src/graphics/texture.c
blob: fb8c1e509f88f8b219ae38c3475104d6ccd8960e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
#include "../../include/mgl/graphics/texture.h"
#include "../../include/mgl/graphics/image.h"
#include "../../include/mgl/mgl.h"
#include <stdio.h>

/* TODO: Check for glTexImage2D/glTexSubImage2D failure */

static const mgl_texture *current_texture = NULL;

static int mgl_texture_format_to_opengl_format(mgl_texture_format format) {
    switch(format) {
        case MGL_TEXTURE_FORMAT_ALPHA:      return GL_ALPHA8;
        case MGL_TEXTURE_FORMAT_GRAY:       return GL_LUMINANCE8;
        case MGL_TEXTURE_FORMAT_GRAY_ALPHA: return GL_LUMINANCE8_ALPHA8;
        case MGL_TEXTURE_FORMAT_RGB:        return GL_RGB8;
        case MGL_TEXTURE_FORMAT_RGBA:       return GL_RGBA8;
    }
    return 0;
}

static int mgl_texture_format_to_compressed_opengl_format(mgl_texture_format format) {
    switch(format) {
        case MGL_TEXTURE_FORMAT_ALPHA:      return GL_ALPHA8;
        case MGL_TEXTURE_FORMAT_GRAY:       return GL_LUMINANCE8;
        case MGL_TEXTURE_FORMAT_GRAY_ALPHA: return GL_LUMINANCE8_ALPHA8;
        case MGL_TEXTURE_FORMAT_RGB:        return GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
        case MGL_TEXTURE_FORMAT_RGBA:       return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
    }
    return 0;
}

static int mgl_texture_format_to_source_opengl_format(mgl_texture_format format) {
    switch(format) {
        case MGL_TEXTURE_FORMAT_ALPHA:      return GL_ALPHA;
        case MGL_TEXTURE_FORMAT_GRAY:       return GL_LUMINANCE;
        case MGL_TEXTURE_FORMAT_GRAY_ALPHA: return GL_LUMINANCE_ALPHA;
        case MGL_TEXTURE_FORMAT_RGB:        return GL_RGB;
        case MGL_TEXTURE_FORMAT_RGBA:       return GL_RGBA;
    }
    return 0;
}

static mgl_texture_format mgl_image_format_to_mgl_texture_format(mgl_image_format image_format) {
    switch(image_format) {
        case MGL_IMAGE_FORMAT_ALPHA:        return MGL_TEXTURE_FORMAT_ALPHA;
        case MGL_IMAGE_FORMAT_GRAY:         return MGL_TEXTURE_FORMAT_GRAY;
        case MGL_IMAGE_FORMAT_GRAY_ALPHA:   return MGL_TEXTURE_FORMAT_GRAY_ALPHA;
        case MGL_IMAGE_FORMAT_RGB:          return MGL_TEXTURE_FORMAT_RGB;
        case MGL_IMAGE_FORMAT_RGBA:         return MGL_TEXTURE_FORMAT_RGBA;
    }
    return 0;
}

int mgl_texture_init(mgl_texture *self) {
    self->id = 0;
    self->width = 0;
    self->height = 0;
    self->format = 0;
    self->max_width = 0;
    self->max_height = 0;
    self->pixel_coordinates = false;

    mgl_context *context = mgl_get_context();
    context->gl.glGenTextures(1, &self->id);
    if(self->id == 0) {
        fprintf(stderr, "Error: failed to init texture (glGenTextures failed)\n");
        return -1;
    }

    int max_texture_size = 0;
    context->gl.glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size);
    self->max_width = max_texture_size;
    self->max_height = max_texture_size;

    return 0;
}

int mgl_texture_load_from_file(mgl_texture *self, const char *filepath, mgl_texture_load_options *load_options) {
    mgl_image image;
    if(mgl_image_load_from_file(&image, filepath) != 0)
        return -1;

    int result = mgl_texture_load_from_image(self, &image, load_options);
    mgl_image_unload(&image);
    return result;
}

int mgl_texture_load_from_image(mgl_texture *self, const mgl_image *image, mgl_texture_load_options *load_options) {
    return mgl_texture_load_from_memory(self, image->data, image->width, image->height, image->format, load_options);
}

int mgl_texture_load_from_memory(mgl_texture *self, const unsigned char *data, int width, int height, mgl_image_format format, mgl_texture_load_options *load_options) {
    if(width < 0 || height < 0)
        return -1;

    if(width > self->max_width || height > self->max_height)
        return -1;

    self->width = width;
    self->height = height;
    self->format = mgl_image_format_to_mgl_texture_format(format);
    self->pixel_coordinates = load_options ? load_options->pixel_coordinates : false;

    int opengl_texture_format = mgl_texture_format_to_opengl_format(self->format);
    if(load_options && load_options->compressed)
        opengl_texture_format = mgl_texture_format_to_compressed_opengl_format(self->format);

    mgl_context *context = mgl_get_context();
    context->gl.glBindTexture(GL_TEXTURE_2D, self->id);
    context->gl.glTexImage2D(GL_TEXTURE_2D, 0, opengl_texture_format, self->width, self->height, 0, mgl_texture_format_to_source_opengl_format(self->format), GL_UNSIGNED_BYTE, data);
    context->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    context->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    context->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    context->gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    context->gl.glBindTexture(GL_TEXTURE_2D, 0);

    return 0;
}

int mgl_texture_update(mgl_texture *self, const unsigned char *data, int offset_x, int offset_y, int width, int height, mgl_image_format format) {
    if(offset_x + width > self->width || offset_y + height > self->height)
        return -1;

    mgl_context *context = mgl_get_context();
    context->gl.glBindTexture(GL_TEXTURE_2D, self->id);
    const mgl_texture_format texture_format = mgl_image_format_to_mgl_texture_format(format);
    context->gl.glTexSubImage2D(GL_TEXTURE_2D, 0, offset_x, offset_y, width, height, mgl_texture_format_to_source_opengl_format(texture_format), GL_UNSIGNED_BYTE, data);
    context->gl.glBindTexture(GL_TEXTURE_2D, 0);
    return 0;
}

int mgl_texture_resize(mgl_texture *self, int new_width, int new_height, mgl_texture_load_options *load_options) {
    if(new_width == self->width && new_height == self->height)
        return 0;

    if(new_width < 0 || new_height < 0)
        return -1;

    if(new_width > self->max_width || new_height > self->max_height)
        return -1;

    self->width = new_width;
    self->height = new_height;

    const int opengl_texture_format = load_options && load_options->compressed ? mgl_texture_format_to_compressed_opengl_format(self->format) : mgl_texture_format_to_opengl_format(self->format);

    mgl_context *context = mgl_get_context();
    context->gl.glBindTexture(GL_TEXTURE_2D, self->id);
    context->gl.glTexImage2D(GL_TEXTURE_2D, 0, opengl_texture_format, self->width, self->height, 0, mgl_texture_format_to_source_opengl_format(self->format), GL_UNSIGNED_BYTE, NULL);
    context->gl.glBindTexture(GL_TEXTURE_2D, 0);
    return 0;
}

/* TODO: Optimize. Do not set matrix if the current coordinate type was the same as the previous one */
void mgl_texture_use(const mgl_texture *texture) {
    mgl_context *context = mgl_get_context();

    current_texture = texture;
    if(texture) {
        float matrix[16] = {
            1.0f, 0.0f, 0.0f, 0.0f,
            0.0f, 1.0f, 0.0f, 0.0f,
            0.0f, 0.0f, 1.0f, 0.0f,
            0.0f, 0.0f, 0.0f, 1.0f,
        };

        if(texture->pixel_coordinates) {
            matrix[0] = 1.0f / (float)texture->width;
            matrix[5] = 1.0f / (float)texture->height;
        }

        context->gl.glMatrixMode(GL_TEXTURE);
        context->gl.glLoadMatrixf(matrix);
        context->gl.glMatrixMode(GL_MODELVIEW);

        context->gl.glBindTexture(GL_TEXTURE_2D, texture->id);
    } else {
        context->gl.glBindTexture(GL_TEXTURE_2D, 0);
    }
}

const mgl_texture* mgl_texture_current_texture(void) {
    return current_texture;
}

void mgl_texture_unload(mgl_texture *self) {
    mgl_context *context = mgl_get_context();
    if(self->id) {
        context->gl.glDeleteTextures(1, &self->id);
        self->id = 0;
    }
}