diff options
Diffstat (limited to 'src/asset/texture.c')
-rw-r--r-- | src/asset/texture.c | 199 |
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 | |||
12 | static 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. | ||
46 | Texture* 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 | } | ||