aboutsummaryrefslogtreecommitdiff
path: root/src/asset/texture.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/asset/texture.c')
-rw-r--r--src/asset/texture.c199
1 files changed, 199 insertions, 0 deletions
diff --git a/src/asset/texture.c b/src/asset/texture.c
new file mode 100644
index 0000000..fb423cc
--- /dev/null
+++ b/src/asset/texture.c
@@ -0,0 +1,199 @@
1#include "texture.h"
2
3#include "gfx/core.h"
4
5#include "error.h"
6
7#define STB_IMAGE_IMPLEMENTATION
8#include "stb_image.h"
9
10#include <assert.h>
11
12static void flip_horizontally(
13 unsigned char* pixels, int width, int height, int components) {
14 assert(pixels);
15
16 for (int y = 0; y < height; ++y) {
17 for (int x = 0; x < width / 2; ++x) {
18 unsigned char* p1 = &pixels[(y * width + x) * components];
19 unsigned char* p2 = &pixels[(y * width + (width - x - 1)) * components];
20
21 for (int c = 0; c < components; ++c) {
22 unsigned char tmp = *p1;
23 *p1 = *p2;
24 *p2 = tmp;
25 p1++;
26 p2++;
27 }
28 }
29 }
30}
31
32// Note that the cubemap coordinate system uses the one in RenderMan:
33//
34// https://www.khronos.org/opengl/wiki/Cubemap_Texture
35//
36// This is what happens:
37//
38// - Cubemaps follow a left-handed coordinate system. Say, +X is right, +Y is
39// up, and +Z is forward.
40// - The texture coordinate system follow's DirectX's, so +V goes down, not up
41// like it does in OpenGL.
42//
43// For this reason, we do X and Y flips when doing cubemap textures so that we
44// can sample cubemaps as if they were given in the usual OpenGL coordinate
45// system.
46Texture* gfx_texture_load(GfxCore* gfxcore, const LoadTextureCmd* cmd) {
47 assert(gfxcore);
48 assert(cmd);
49 assert(cmd->origin == AssetFromFile || cmd->origin == AssetFromMemory);
50 assert(cmd->type == LoadTexture || cmd->type == LoadCubemap);
51
52 int width, height, components;
53 unsigned char* pixels[6] = {0};
54
55 switch (cmd->origin) {
56 case AssetFromFile:
57 switch (cmd->type) {
58 case LoadTexture: {
59 const char* filepath = mstring_cstr(&cmd->data.texture.filepath);
60 stbi_set_flip_vertically_on_load(0);
61 pixels[0] = stbi_load(filepath, &width, &height, &components, 0);
62 if (!pixels[0]) {
63 log_error("Failed to load texture file: %s", filepath);
64 }
65 break;
66 }
67 case LoadCubemap: {
68 int old_components = 0;
69 for (int i = 0; i < 6; ++i) {
70 // Flip +Y and -Y textures vertically.
71 stbi_set_flip_vertically_on_load(((i == 2) || (i == 3)) ? 1 : 0);
72 const char* filepath =
73 mstring_cstr(&cmd->data.cubemap.filepaths.filepath_pos_x + i);
74 stbi_uc* image_pixels =
75 stbi_load(filepath, &width, &height, &components, 0);
76 if (!image_pixels) {
77 log_error("Failed to load texture file: %s", filepath);
78 break;
79 }
80 if ((i > 0) && (components != old_components)) {
81 log_error(
82 "All textures in a cubemap must have the same number of "
83 "components");
84 break;
85 }
86 if ((i != 2) && (i != 3)) {
87 flip_horizontally(image_pixels, width, height, components);
88 }
89 pixels[i] = image_pixels;
90 old_components = components;
91 }
92 break;
93 }
94 }
95 break;
96 case AssetFromMemory:
97 // TODO: Load textures from memory.
98 log_error("Loading textures from memory is not yet implemented");
99 return 0;
100 }
101
102 // Error out if we failed to load a texture.
103 if (!pixels[0] ||
104 (cmd->type == LoadCubemap &&
105 (!pixels[1] || !pixels[2] || !pixels[3] || !pixels[4] || !pixels[5]))) {
106 for (int i = 0; i < 6; ++i) {
107 if (pixels[i]) {
108 stbi_image_free(pixels[i]);
109 }
110 }
111 return 0;
112 }
113
114 TextureDesc desc = (TextureDesc){0};
115 desc.width = width;
116 desc.height = height;
117
118 switch (cmd->type) {
119 case LoadTexture:
120 desc.dimension = Texture2D;
121 break;
122 case LoadCubemap:
123 desc.dimension = TextureCubeMap;
124 break;
125 }
126
127 switch (components) {
128 case 1:
129 switch (cmd->colour_space) {
130 case LinearColourSpace:
131 desc.format = TextureR8;
132 break;
133 case sRGB:
134 // TODO: Gamma single-channel textures are not implemented yet.
135 // The caller should convert the single-channel to RGB and pass it down
136 // as sRGB. This is why the ChronographWatch currently appears red on the
137 // back.
138 log_error("Gamma colour space is not supported for 1-channel textures");
139 // return 0;
140 desc.format = TextureR8;
141 break;
142 default:
143 log_error("Unsupported texture colour space: %d", cmd->colour_space);
144 return 0;
145 }
146 break;
147 case 3:
148 switch (cmd->colour_space) {
149 case LinearColourSpace:
150 desc.format = TextureRGB8;
151 break;
152 case sRGB:
153 desc.format = TextureSRGB8;
154 break;
155 default:
156 log_error("Unsupported texture colour space: %d", cmd->colour_space);
157 return 0;
158 }
159 break;
160 case 4:
161 switch (cmd->colour_space) {
162 case LinearColourSpace:
163 desc.format = TextureRGBA8;
164 break;
165 case sRGB:
166 desc.format = TextureSRGBA8;
167 break;
168 default:
169 log_error("Unsupported texture colour space: %d", cmd->colour_space);
170 return 0;
171 }
172 break;
173 default:
174 log_error("Unsupported number of texture components: %d", components);
175 return 0;
176 }
177
178 desc.filtering = cmd->filtering;
179 desc.mipmaps = cmd->mipmaps;
180
181 switch (cmd->type) {
182 case LoadTexture:
183 desc.data.pixels = pixels[0];
184 break;
185 case LoadCubemap:
186 for (int i = 0; i < 6; ++i) {
187 *(&desc.data.cubemap.pixels_pos_x + i) = pixels[i];
188 }
189 break;
190 }
191
192 Texture* texture = gfx_make_texture(gfxcore, &desc);
193 for (int i = 0; i < 6; ++i) {
194 if (pixels[i]) {
195 stbi_image_free(pixels[i]);
196 }
197 }
198 return texture;
199}