#include "TextureAssetLoader.h" #include #include #include #include #include #include #include #include #include #include #ifndef GL_RGBA32F #define GL_RGBA32F 0x8814 #endif namespace { std::string LowercaseExtension(const std::filesystem::path& path) { std::string extension = path.extension().string(); std::transform(extension.begin(), extension.end(), extension.begin(), [](unsigned char value) { return static_cast(std::tolower(value)); }); return extension; } bool LoadCubeTextureAsset(const ShaderTextureAsset& textureAsset, GLuint& textureId, std::string& error) { std::ifstream file(textureAsset.path); if (!file) { error = "Could not open shader LUT asset: " + textureAsset.path.string(); return false; } unsigned lutSize = 0; std::vector values; std::string line; while (std::getline(file, line)) { const std::size_t commentStart = line.find('#'); if (commentStart != std::string::npos) line.resize(commentStart); std::istringstream stream(line); std::string firstToken; if (!(stream >> firstToken)) continue; if (firstToken == "TITLE" || firstToken == "DOMAIN_MIN" || firstToken == "DOMAIN_MAX") continue; if (firstToken == "LUT_3D_SIZE") { stream >> lutSize; continue; } if (firstToken == "LUT_1D_SIZE") { error = "Only 3D .cube LUT assets are supported: " + textureAsset.path.string(); return false; } float red = 0.0f; float green = 0.0f; float blue = 0.0f; try { red = std::stof(firstToken); } catch (...) { error = "Unsupported .cube directive in shader LUT asset: " + firstToken; return false; } if (!(stream >> green >> blue)) { error = "Malformed RGB entry in shader LUT asset: " + textureAsset.path.string(); return false; } values.push_back(red); values.push_back(green); values.push_back(blue); values.push_back(1.0f); } if (lutSize == 0) { error = "Shader LUT asset is missing LUT_3D_SIZE: " + textureAsset.path.string(); return false; } const std::size_t expectedFloats = static_cast(lutSize) * lutSize * lutSize * 4; if (values.size() != expectedFloats) { error = "Shader LUT asset entry count does not match LUT_3D_SIZE: " + textureAsset.path.string(); return false; } const GLsizei atlasWidth = static_cast(lutSize * lutSize); const GLsizei atlasHeight = static_cast(lutSize); glGenTextures(1, &textureId); glBindTexture(GL_TEXTURE_2D, textureId); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glPixelStorei(GL_UNPACK_ALIGNMENT, 4); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, atlasWidth, atlasHeight, 0, GL_RGBA, GL_FLOAT, values.data()); glBindTexture(GL_TEXTURE_2D, 0); return true; } } bool LoadTextureAsset(const ShaderTextureAsset& textureAsset, GLuint& textureId, std::string& error) { textureId = 0; if (LowercaseExtension(textureAsset.path) == ".cube") return LoadCubeTextureAsset(textureAsset, textureId, error); HRESULT comInitResult = CoInitializeEx(NULL, COINIT_MULTITHREADED); const bool shouldUninitializeCom = (comInitResult == S_OK || comInitResult == S_FALSE); if (FAILED(comInitResult) && comInitResult != RPC_E_CHANGED_MODE) { error = "Could not initialize COM to load shader texture assets."; return false; } CComPtr imagingFactory; HRESULT result = CoCreateInstance(CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&imagingFactory)); if (FAILED(result) || !imagingFactory) { if (shouldUninitializeCom) CoUninitialize(); error = "Could not create a WIC imaging factory to load shader texture assets."; return false; } CComPtr bitmapDecoder; result = imagingFactory->CreateDecoderFromFilename(textureAsset.path.wstring().c_str(), NULL, GENERIC_READ, WICDecodeMetadataCacheOnLoad, &bitmapDecoder); if (FAILED(result) || !bitmapDecoder) { if (shouldUninitializeCom) CoUninitialize(); error = "Could not open shader texture asset: " + textureAsset.path.string(); return false; } CComPtr bitmapFrame; result = bitmapDecoder->GetFrame(0, &bitmapFrame); if (FAILED(result) || !bitmapFrame) { if (shouldUninitializeCom) CoUninitialize(); error = "Could not decode the first frame of shader texture asset: " + textureAsset.path.string(); return false; } CComPtr formatConverter; result = imagingFactory->CreateFormatConverter(&formatConverter); if (FAILED(result) || !formatConverter) { if (shouldUninitializeCom) CoUninitialize(); error = "Could not create a WIC format converter for shader texture asset: " + textureAsset.path.string(); return false; } result = formatConverter->Initialize(bitmapFrame, GUID_WICPixelFormat32bppBGRA, WICBitmapDitherTypeNone, NULL, 0.0, WICBitmapPaletteTypeCustom); if (FAILED(result)) { if (shouldUninitializeCom) CoUninitialize(); error = "Could not convert shader texture asset to BGRA: " + textureAsset.path.string(); return false; } UINT width = 0; UINT height = 0; result = formatConverter->GetSize(&width, &height); if (FAILED(result) || width == 0 || height == 0) { if (shouldUninitializeCom) CoUninitialize(); error = "Shader texture asset has an invalid size: " + textureAsset.path.string(); return false; } const UINT stride = width * 4; std::vector pixels(static_cast(stride) * static_cast(height)); result = formatConverter->CopyPixels(NULL, stride, static_cast(pixels.size()), pixels.data()); if (FAILED(result)) { if (shouldUninitializeCom) CoUninitialize(); error = "Could not read shader texture pixels: " + textureAsset.path.string(); return false; } std::vector flippedPixels(pixels.size()); for (UINT row = 0; row < height; ++row) { const std::size_t srcOffset = static_cast(row) * stride; const std::size_t dstOffset = static_cast(height - 1 - row) * stride; std::memcpy(flippedPixels.data() + dstOffset, pixels.data() + srcOffset, stride); } glGenTextures(1, &textureId); glBindTexture(GL_TEXTURE_2D, textureId); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, static_cast(width), static_cast(height), 0, GL_BGRA, GL_UNSIGNED_BYTE, flippedPixels.data()); glBindTexture(GL_TEXTURE_2D, 0); if (shouldUninitializeCom) CoUninitialize(); return true; }