Skip to content

Commit

Permalink
IGL: Add load helpers to texture_loader
Browse files Browse the repository at this point in the history
Summary: This diff adds new load and loadToExternalMemory helper methods to texture_loader. These are intended to support use cases where the texture data is not directly in a form usable by the GPU (e.g., png, jpeg). A new memorySizeInBytes is also added to help allocate memory to load the data to. A new supporting class, IData, is also introduced to hold data when the class itself must allocate the data.

Reviewed By: MichaelTay

Differential Revision: D49135958

fbshipit-source-id: 59d050e2a125b6f9c6b138ca765bebeb4abed3cd
  • Loading branch information
Eric Griffith authored and facebook-github-bot committed Sep 13, 2023
1 parent ae1ed3e commit f060edf
Show file tree
Hide file tree
Showing 6 changed files with 276 additions and 22 deletions.
56 changes: 56 additions & 0 deletions IGLU/texture_loader/IData.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#include <IGLU/texture_loader/IData.h>

namespace iglu::textureloader {
namespace {
class ByteData final : public IData {
public:
ByteData(std::unique_ptr<uint8_t[]> data, size_t length) noexcept;

~ByteData() final = default;

[[nodiscard]] const uint8_t* IGL_NONNULL data() const noexcept final;
[[nodiscard]] uint32_t length() const noexcept final;

private:
std::unique_ptr<uint8_t[]> data_;
uint32_t length_ = 0;
};

ByteData::ByteData(std::unique_ptr<uint8_t[]> data, size_t length) noexcept :
data_(std::move(data)), length_(length) {}

const uint8_t* IGL_NONNULL ByteData::data() const noexcept {
IGL_ASSERT(data_ != nullptr);
return data_.get();
}

uint32_t ByteData::length() const noexcept {
return length_;
}

} // namespace

std::unique_ptr<IData> IData::tryCreate(std::unique_ptr<uint8_t[]> data,
uint32_t length,
igl::Result* IGL_NULLABLE outResult) {
if (data == nullptr) {
igl::Result::setResult(outResult, igl::Result::Code::ArgumentNull, "data is nullptr");
return nullptr;
}

if (length == 0u) {
igl::Result::setResult(outResult, igl::Result::Code::ArgumentInvalid, "length is 0");
return nullptr;
}

return std::make_unique<ByteData>(std::move(data), length);
}

} // namespace iglu::textureloader
31 changes: 31 additions & 0 deletions IGLU/texture_loader/IData.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#pragma once

#include <igl/Common.h>
#include <memory>

namespace iglu::textureloader {

/// Interface for accessing data.
class IData {
protected:
IData() noexcept = default;

public:
virtual ~IData() = default;

static std::unique_ptr<IData> tryCreate(std::unique_ptr<uint8_t[]> data,
uint32_t length,
igl::Result* IGL_NULLABLE outResult);

[[nodiscard]] virtual const uint8_t* IGL_NONNULL data() const noexcept = 0;
[[nodiscard]] virtual uint32_t length() const noexcept = 0;
};

} // namespace iglu::textureloader
77 changes: 77 additions & 0 deletions IGLU/texture_loader/ITextureLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@

#include <IGLU/texture_loader/ITextureLoader.h>

#include <IGLU/texture_loader/IData.h>
#include <igl/Device.h>
#include <igl/IGLSafeC.h>

namespace iglu::textureloader {

Expand All @@ -26,6 +28,20 @@ const igl::TextureDesc& ITextureLoader::descriptor() const noexcept {
return desc_;
}

[[nodiscard]] uint32_t ITextureLoader::memorySizeInBytes() const noexcept {
const auto properties = igl::TextureFormatProperties::fromTextureFormat(desc_.format);
igl::TextureRangeDesc range;
range.width = desc_.width;
range.height = desc_.height;
range.depth = desc_.depth;
range.numFaces = desc_.type == igl::TextureType::Cube ? static_cast<size_t>(6)
: static_cast<size_t>(1);
range.numLayers = desc_.numLayers;
range.numMipLevels = desc_.numMipLevels;

return static_cast<uint32_t>(properties.getBytesPerRange(range));
}

bool ITextureLoader::isSupported(const igl::ICapabilities& capabilities) const noexcept {
return isSupported(capabilities, desc_.usage);
}
Expand Down Expand Up @@ -87,6 +103,25 @@ void ITextureLoader::upload(igl::ITexture& texture,
uploadInternal(texture, outResult);
}

std::unique_ptr<IData> ITextureLoader::load(igl::Result* IGL_NULLABLE outResult) const noexcept {
return loadInternal(outResult);
}

void ITextureLoader::loadToExternalMemory(uint8_t* IGL_NONNULL data,
uint32_t length,
igl::Result* IGL_NULLABLE outResult) const noexcept {
if (data == nullptr) {
igl::Result::setResult(outResult, igl::Result::Code::ArgumentNull, "data is nullptr.");
return;
}
if (length < memorySizeInBytes()) {
igl::Result::setResult(outResult, igl::Result::Code::ArgumentInvalid, "length is too short.");
return;
}

return loadToExternalMemoryInternal(data, length, outResult);
}

DataReader& ITextureLoader::reader() noexcept {
return reader_;
}
Expand All @@ -95,4 +130,46 @@ const DataReader& ITextureLoader::reader() const noexcept {
return reader_;
}

void ITextureLoader::defaultUpload(igl::ITexture& texture,
igl::Result* IGL_NULLABLE outResult) const noexcept {
std::unique_ptr<IData> data;

if (!canUploadSourceData()) {
data = load(outResult);
if (!data) {
return;
}
}

const auto range = shouldGenerateMipmaps() ? texture.getFullRange() : texture.getFullMipRange();
auto result = texture.upload(range, data ? data->data() : reader_.data());
igl::Result::setResult(outResult, std::move(result));
}

std::unique_ptr<IData> ITextureLoader::defaultLoad(
igl::Result* IGL_NULLABLE outResult) const noexcept {
const uint32_t length = memorySizeInBytes();
auto data = std::make_unique<uint8_t[]>(length);
if (!data) {
igl::Result::setResult(outResult, igl::Result::Code::RuntimeError, "out of memory.");
return nullptr;
}

loadToExternalMemory(data.get(), length, outResult);

return IData::tryCreate(std::move(data), length, outResult);
}

void ITextureLoader::defaultLoadToExternalMemory(uint8_t* IGL_NONNULL data,
uint32_t length,
igl::Result* IGL_NULLABLE
outResult) const noexcept {
if (reader_.length() != length) {
igl::Result::setResult(
outResult, igl::Result::Code::ArgumentInvalid, "length doesn't match reader length.");
return;
}
checked_memcpy(data, length, reader_.data(), length);
}

} // namespace iglu::textureloader
36 changes: 34 additions & 2 deletions IGLU/texture_loader/ITextureLoader.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#pragma once

#include <IGLU/texture_loader/DataReader.h>
#include <IGLU/texture_loader/IData.h>
#include <igl/Texture.h>

namespace igl {
Expand All @@ -27,6 +28,8 @@ class ITextureLoader {

[[nodiscard]] const igl::TextureDesc& descriptor() const noexcept;

[[nodiscard]] uint32_t memorySizeInBytes() const noexcept;

[[nodiscard]] bool isSupported(const igl::ICapabilities& capabilities) const noexcept;
[[nodiscard]] bool isSupported(const igl::ICapabilities& capabilities,
igl::TextureDesc::TextureUsage usage) const noexcept;
Expand All @@ -39,8 +42,20 @@ class ITextureLoader {
igl::Result* IGL_NULLABLE
outResult) const noexcept;

[[nodiscard]] virtual bool canUploadSourceData() const noexcept {
return false;
}
[[nodiscard]] virtual bool canUseExternalMemory() const noexcept {
return false;
}

void upload(igl::ITexture& texture, igl::Result* IGL_NULLABLE outResult) const noexcept;

[[nodiscard]] std::unique_ptr<IData> load(igl::Result* IGL_NULLABLE outResult) const noexcept;
void loadToExternalMemory(uint8_t* IGL_NONNULL data,
uint32_t length,
igl::Result* IGL_NULLABLE outResult) const noexcept;

[[nodiscard]] virtual bool shouldGenerateMipmaps() const noexcept {
return desc_.numMipLevels > 1;
}
Expand All @@ -53,11 +68,28 @@ class ITextureLoader {

virtual void uploadInternal(igl::ITexture& texture,
igl::Result* IGL_NULLABLE outResult) const noexcept {
auto result = texture.upload(texture.getFullMipRange(), reader_.data());
igl::Result::setResult(outResult, std::move(result));
defaultUpload(texture, outResult);
}

[[nodiscard]] virtual std::unique_ptr<IData> loadInternal(
igl::Result* IGL_NULLABLE outResult) const noexcept {
return defaultLoad(outResult);
}

virtual void loadToExternalMemoryInternal(uint8_t* IGL_NONNULL data,
uint32_t length,
igl::Result* IGL_NULLABLE outResult) const noexcept {
defaultLoadToExternalMemory(data, length, outResult);
}

private:
void defaultUpload(igl::ITexture& texture, igl::Result* IGL_NULLABLE outResult) const noexcept;
[[nodiscard]] std::unique_ptr<IData> defaultLoad(
igl::Result* IGL_NULLABLE outResult) const noexcept;
void defaultLoadToExternalMemory(uint8_t* IGL_NONNULL data,
uint32_t length,
igl::Result* IGL_NULLABLE outResult) const noexcept;

igl::TextureDesc desc_;
DataReader reader_;
};
Expand Down
50 changes: 40 additions & 10 deletions IGLU/texture_loader/ktx1/TextureLoaderFactory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,47 @@
#include <IGLU/texture_loader/ktx1/TextureLoaderFactory.h>

#include <IGLU/texture_loader/ktx1/Header.h>
#include <igl/IGLSafeC.h>
#include <vector>

namespace iglu::textureloader::ktx1 {
namespace {

struct MipLevelData {
const uint8_t* data = nullptr;
uint32_t length = 0u;
};

class TextureLoader : public ITextureLoader {
using Super = ITextureLoader;

public:
TextureLoader(DataReader reader,
const igl::TextureRangeDesc& range,
igl::TextureFormat format,
std::vector<const uint8_t*> mipData) noexcept;
std::vector<MipLevelData> mipLevelData) noexcept;

[[nodiscard]] bool canUploadSourceData() const noexcept final;
[[nodiscard]] bool shouldGenerateMipmaps() const noexcept final;

private:
void uploadInternal(igl::ITexture& texture,
igl::Result* IGL_NULLABLE outResult) const noexcept final;
void loadToExternalMemoryInternal(uint8_t* IGL_NONNULL data,
uint32_t length,
igl::Result* IGL_NULLABLE outResult) const noexcept final;

std::vector<const uint8_t*> mipData_;
std::vector<MipLevelData> mipLevelData_;
bool shouldGenerateMipmaps_ = false;
};

TextureLoader::TextureLoader(DataReader reader,
const igl::TextureRangeDesc& range,
igl::TextureFormat format,
std::vector<const uint8_t*> mipData) noexcept :
Super(reader), mipData_(std::move(mipData)), shouldGenerateMipmaps_(range.numMipLevels == 0) {
std::vector<MipLevelData> mipLevelData) noexcept :
Super(reader),
mipLevelData_(std::move(mipLevelData)),
shouldGenerateMipmaps_(range.numMipLevels == 0) {
auto& desc = mutableDescriptor();
desc.format = format;
desc.numMipLevels = range.numMipLevels;
Expand All @@ -56,6 +68,10 @@ TextureLoader::TextureLoader(DataReader reader,
}
}

bool TextureLoader::canUploadSourceData() const noexcept {
return true;
}

bool TextureLoader::shouldGenerateMipmaps() const noexcept {
return shouldGenerateMipmaps_;
}
Expand All @@ -64,13 +80,25 @@ void TextureLoader::uploadInternal(igl::ITexture& texture,
igl::Result* IGL_NULLABLE outResult) const noexcept {
const auto& desc = descriptor();

for (size_t mipLevel = 0; mipLevel < desc.numMipLevels && mipLevel < mipData_.size();
for (size_t mipLevel = 0; mipLevel < desc.numMipLevels && mipLevel < mipLevelData_.size();
++mipLevel) {
texture.upload(texture.getFullRange(mipLevel), mipData_[mipLevel]);
texture.upload(texture.getFullRange(mipLevel), mipLevelData_[mipLevel].data);
}

igl::Result::setOk(outResult);
}

void TextureLoader::loadToExternalMemoryInternal(uint8_t* IGL_NONNULL data,
uint32_t length,
igl::Result* IGL_NULLABLE
/*outResult*/) const noexcept {
uint32_t offset = 0;
for (const auto& mipLevelData : mipLevelData_) {
checked_memcpy_offset(data, length, offset, mipLevelData.data, mipLevelData.length);
offset += mipLevelData.length;
}
}

} // namespace

uint32_t TextureLoaderFactory::headerLength() const noexcept {
Expand Down Expand Up @@ -185,8 +213,8 @@ std::unique_ptr<ITextureLoader> TextureLoaderFactory::tryCreateInternal(
return nullptr;
}

std::vector<const uint8_t*> mipData;
mipData.reserve(range.numMipLevels);
std::vector<MipLevelData> mipLevelData;
mipLevelData.reserve(range.numMipLevels);

const bool isCubeTexture = header->numberOfFaces == 6u;

Expand All @@ -202,11 +230,13 @@ std::unique_ptr<ITextureLoader> TextureLoaderFactory::tryCreateInternal(
return nullptr;
}
offset += 4u;
mipData.emplace_back(reader.at(offset));
mipLevelData.emplace_back(
MipLevelData{reader.at(offset),
static_cast<uint32_t>(isCubeTexture ? expectedCubeBytes : expectedBytes)});
offset += static_cast<uint32_t>(isCubeTexture ? expectedCubeBytes : expectedBytes);
}

return std::make_unique<TextureLoader>(reader, range, properties.format, std::move(mipData));
return std::make_unique<TextureLoader>(reader, range, properties.format, std::move(mipLevelData));
}

} // namespace iglu::textureloader::ktx1
Loading

0 comments on commit f060edf

Please sign in to comment.