#include "texture.h" #include "gfx/core.h" #include "error.h" #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" #include static void flip_horizontally( unsigned char* pixels, int width, int height, int components) { assert(pixels); for (int y = 0; y < height; ++y) { for (int x = 0; x < width / 2; ++x) { unsigned char* p1 = &pixels[(y * width + x) * components]; unsigned char* p2 = &pixels[(y * width + (width - x - 1)) * components]; for (int c = 0; c < components; ++c) { unsigned char tmp = *p1; *p1 = *p2; *p2 = tmp; p1++; p2++; } } } } // Note that the cubemap coordinate system uses the one in RenderMan: // // https://www.khronos.org/opengl/wiki/Cubemap_Texture // // This is what happens: // // - Cubemaps follow a left-handed coordinate system. Say, +X is right, +Y is // up, and +Z is forward. // - The texture coordinate system follow's DirectX's, so +V goes down, not up // like it does in OpenGL. // // For this reason, we do X and Y flips when doing cubemap textures so that we // can sample cubemaps as if they were given in the usual OpenGL coordinate // system. Texture* gfx_texture_load(GfxCore* gfxcore, const LoadTextureCmd* cmd) { assert(gfxcore); assert(cmd); assert(cmd->origin == AssetFromFile || cmd->origin == AssetFromMemory); assert(cmd->type == LoadTexture || cmd->type == LoadCubemap); int width, height, components, old_components; unsigned char* pixels[6] = {0}; switch (cmd->origin) { case AssetFromFile: switch (cmd->type) { case LoadTexture: { const char* filepath = mstring_cstr(&cmd->data.texture.filepath); stbi_set_flip_vertically_on_load(0); pixels[0] = stbi_load(filepath, &width, &height, &components, 0); if (!pixels[0]) { log_error("Failed to load texture file: %s", filepath); } break; } case LoadCubemap: for (int i = 0; i < 6; ++i) { // Flip +Y and -Y textures vertically. stbi_set_flip_vertically_on_load(((i == 2) || (i == 3)) ? 1 : 0); const char* filepath = mstring_cstr(&cmd->data.cubemap.filepaths.filepath_pos_x + i); stbi_uc* image_pixels = stbi_load(filepath, &width, &height, &components, 0); if (!image_pixels) { log_error("Failed to load texture file: %s", filepath); break; } if (i > 0 && components != old_components) { log_error("All textures in a cubemap must have the same number of " "components"); break; } if ((i != 2) && (i != 3)) { flip_horizontally(image_pixels, width, height, components); } pixels[i] = image_pixels; old_components = components; } break; } break; case AssetFromMemory: // TODO: Load textures from memory. log_error("Loading textures from memory is not yet implemented"); return 0; } // Error out if we failed to load a texture. if (!pixels[0] || (cmd->type == LoadCubemap && (!pixels[1] || !pixels[2] || !pixels[3] || !pixels[4] || !pixels[5]))) { for (int i = 0; i < 6; ++i) { if (pixels[i]) { stbi_image_free(pixels[i]); } } return 0; } TextureDesc desc = (TextureDesc){0}; desc.width = width; desc.height = height; switch (cmd->type) { case LoadTexture: desc.dimension = Texture2D; break; case LoadCubemap: desc.dimension = TextureCubeMap; break; } switch (components) { case 3: switch (cmd->colour_space) { case LinearColourSpace: desc.format = TextureRGB8; break; case sRGB: desc.format = TextureSRGB8; break; default: log_error("Unsupported texture colour space: %d", cmd->colour_space); return 0; } break; case 4: switch (cmd->colour_space) { case LinearColourSpace: desc.format = TextureRGBA8; break; case sRGB: desc.format = TextureSRGBA8; break; default: log_error("Unsupported texture colour space: %d", cmd->colour_space); return 0; } break; default: log_error("Unsupported number of texture components: %d", components); return 0; } desc.filtering = cmd->filtering; desc.mipmaps = cmd->mipmaps; switch (cmd->type) { case LoadTexture: desc.data.pixels = pixels[0]; break; case LoadCubemap: for (int i = 0; i < 6; ++i) { *(&desc.data.cubemap.pixels_pos_x + i) = pixels[i]; } break; } Texture* texture = gfx_make_texture(gfxcore, &desc); for (int i = 0; i < 6; ++i) { if (pixels[i]) { stbi_image_free(pixels[i]); } } return texture; }