Added support for R11G11B10F and RGB9E5 cubemaps. Weird colors though but it doesn't crash

This commit is contained in:
Olivier Prat 2017-09-13 14:32:22 +02:00
parent bf32f65efe
commit aab63bf109
8 changed files with 199 additions and 54 deletions

View file

@ -19,6 +19,7 @@ bool GLTexelFormat::isCompressed() const {
case GL_COMPRESSED_RED_RGTC1:
case GL_COMPRESSED_RG_RGTC2:
case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM:
case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT:
return true;
default:
return false;
@ -94,6 +95,11 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) {
result = GL_R11F_G11F_B10F;
break;
case gpu::RGB9E5:
// the type should be float
result = GL_RGB9_E5;
break;
case gpu::DEPTH:
result = GL_DEPTH_COMPONENT32;
switch (dstFormat.getType()) {
@ -244,6 +250,9 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) {
case gpu::COMPRESSED_BC5_XY:
result = GL_COMPRESSED_RG_RGTC2;
break;
case gpu::COMPRESSED_BC6_RGB:
result = GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT;
break;
case gpu::COMPRESSED_BC7_SRGBA:
result = GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM;
break;
@ -396,6 +405,9 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E
case gpu::COMPRESSED_BC5_XY:
texel.internalFormat = GL_COMPRESSED_RG_RGTC2;
break;
case gpu::COMPRESSED_BC6_RGB:
texel.internalFormat = GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT;
break;
case gpu::COMPRESSED_BC7_SRGBA:
texel.internalFormat = GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM;
break;
@ -495,10 +507,18 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E
case gpu::R11G11B10:
texel.format = GL_RGB;
texel.type = GL_UNSIGNED_INT_10F_11F_11F_REV;
// the type should be float
texel.internalFormat = GL_R11F_G11F_B10F;
break;
case gpu::RGB9E5:
texel.format = GL_RGB;
texel.type = GL_UNSIGNED_INT_5_9_9_9_REV;
// the type should be float
texel.internalFormat = GL_RGB9_E5;
break;
case gpu::DEPTH:
texel.format = GL_DEPTH_COMPONENT; // It's depth component to load it
texel.internalFormat = GL_DEPTH_COMPONENT32;
@ -694,6 +714,9 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E
case gpu::COMPRESSED_BC5_XY:
texel.internalFormat = GL_COMPRESSED_RG_RGTC2;
break;
case gpu::COMPRESSED_BC6_RGB:
texel.internalFormat = GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT;
break;
case gpu::COMPRESSED_BC7_SRGBA:
texel.internalFormat = GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM;
break;

View file

@ -114,6 +114,7 @@ Size GL41Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const
case GL_COMPRESSED_RED_RGTC1:
case GL_COMPRESSED_RG_RGTC2:
case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM:
case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT:
glCompressedTexSubImage2D(_target, mip, 0, yOffset, size.x, size.y, internalFormat,
static_cast<GLsizei>(sourceSize), sourcePointer);
break;
@ -131,6 +132,7 @@ Size GL41Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const
case GL_COMPRESSED_RED_RGTC1:
case GL_COMPRESSED_RG_RGTC2:
case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM:
case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT:
glCompressedTexSubImage2D(target, mip, 0, yOffset, size.x, size.y, internalFormat,
static_cast<GLsizei>(sourceSize), sourcePointer);
break;

View file

@ -143,6 +143,7 @@ Size GL45Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const
case GL_COMPRESSED_RED_RGTC1:
case GL_COMPRESSED_RG_RGTC2:
case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM:
case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT:
glCompressedTextureSubImage2D(_id, mip, 0, yOffset, size.x, size.y, internalFormat,
static_cast<GLsizei>(sourceSize), sourcePointer);
break;
@ -158,6 +159,7 @@ Size GL45Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const
case GL_COMPRESSED_RED_RGTC1:
case GL_COMPRESSED_RG_RGTC2:
case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM:
case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT:
if (glCompressedTextureSubImage2DEXT) {
auto target = GLTexture::CUBE_FACE_LAYOUT[face];
glCompressedTextureSubImage2DEXT(_id, target, mip, 0, yOffset, size.x, size.y, internalFormat,

View file

@ -24,11 +24,13 @@ const Element Element::COLOR_COMPRESSED_SRGB { TILE4x4, COMPRESSED, COMPRESSED_B
const Element Element::COLOR_COMPRESSED_SRGBA_MASK { TILE4x4, COMPRESSED, COMPRESSED_BC1_SRGBA };
const Element Element::COLOR_COMPRESSED_SRGBA { TILE4x4, COMPRESSED, COMPRESSED_BC3_SRGBA };
const Element Element::COLOR_COMPRESSED_XY { TILE4x4, COMPRESSED, COMPRESSED_BC5_XY };
const Element Element::COLOR_COMPRESSED_SRGBA_HIGH { TILE4x4, COMPRESSED, COMPRESSED_BC7_SRGBA };
const Element Element::COLOR_COMPRESSED_SRGBA_HIGH{ TILE4x4, COMPRESSED, COMPRESSED_BC7_SRGBA };
const Element Element::COLOR_COMPRESSED_HDR_RGB{ TILE4x4, COMPRESSED, COMPRESSED_BC6_RGB };
const Element Element::VEC2NU8_XY{ VEC2, NUINT8, XY };
const Element Element::COLOR_R11G11B10{ SCALAR, FLOAT, R11G11B10 };
const Element Element::COLOR_RGB9E5{ SCALAR, FLOAT, RGB9E5 };
const Element Element::VEC4F_COLOR_RGBA{ VEC4, FLOAT, RGBA };
const Element Element::VEC2F_UV{ VEC2, FLOAT, UV };
const Element Element::VEC2F_XY{ VEC2, FLOAT, XY };

View file

@ -187,11 +187,13 @@ enum Semantic : uint8_t {
COMPRESSED_BC3_SRGBA,
COMPRESSED_BC4_RED,
COMPRESSED_BC5_XY,
COMPRESSED_BC6_RGB,
COMPRESSED_BC7_SRGBA,
_LAST_COMPRESSED,
R11G11B10,
RGB9E5,
UNIFORM,
UNIFORM_BUFFER,
@ -240,11 +242,13 @@ static const int SEMANTIC_SIZE_FACTOR[NUM_SEMANTICS] = {
16, //COMPRESSED_BC3_SRGBA, 1 byte/pixel * 4x4 pixels = 16 bytes
8, //COMPRESSED_BC4_RED, 1/2 byte/pixel * 4x4 pixels = 8 bytes
16, //COMPRESSED_BC5_XY, 1 byte/pixel * 4x4 pixels = 16 bytes
16, //COMPRESSED_BC6_RGB, 1 byte/pixel * 4x4 pixels = 16 bytes
16, //COMPRESSED_BC7_SRGBA, 1 byte/pixel * 4x4 pixels = 16 bytes
1, //_LAST_COMPRESSED,
1, //R11G11B10,
1, //RGB9E5
1, //UNIFORM,
1, //UNIFORM_BUFFER,
@ -306,12 +310,14 @@ public:
static const Element COLOR_BGRA_32;
static const Element COLOR_SBGRA_32;
static const Element COLOR_R11G11B10;
static const Element COLOR_RGB9E5;
static const Element COLOR_COMPRESSED_RED;
static const Element COLOR_COMPRESSED_SRGB;
static const Element COLOR_COMPRESSED_SRGBA_MASK;
static const Element COLOR_COMPRESSED_SRGBA;
static const Element COLOR_COMPRESSED_XY;
static const Element COLOR_COMPRESSED_SRGBA_HIGH;
static const Element COLOR_COMPRESSED_HDR_RGB;
static const Element VEC2NU8_XY;
static const Element VEC4F_COLOR_RGBA;
static const Element VEC2F_UV;

View file

@ -14,6 +14,7 @@
#include <glm/gtc/constants.hpp>
#include <glm/gtx/component_wise.hpp>
#include <glm/gtc/packing.hpp>
#include <QtCore/QDebug>
#include <QtCore/QThread>
@ -683,6 +684,22 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector<
PROFILE_RANGE(render_gpu, "sphericalHarmonicsFromTexture");
auto mipFormat = cubeTexture.getStoredMipFormat();
std::function<glm::vec3(uint32)> unpackFunc;
switch (mipFormat.getSemantic())
{
case gpu::R11G11B10:
unpackFunc = glm::unpackF2x11_1x10;
break;
case gpu::RGB9E5:
unpackFunc = glm::unpackF3x9_E1x5;
break;
default:
assert(false);
break;
}
assert(mipFormat.getSemantic() == gpu::R11G11B10);
const uint sqOrder = order*order;
// allocate memory for calculations
@ -716,17 +733,7 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector<
for(int face=0; face < gpu::Texture::NUM_CUBE_FACES; face++) {
PROFILE_RANGE(render_gpu, "ProcessFace");
auto mipFormat = cubeTexture.getStoredMipFormat();
auto numComponents = mipFormat.getScalarCount();
int roffset { 0 };
int goffset { 1 };
int boffset { 2 };
if ((mipFormat.getSemantic() == gpu::BGRA) || (mipFormat.getSemantic() == gpu::SBGRA)) {
roffset = 2;
boffset = 0;
}
auto data = cubeTexture.accessStoredMipFace(0, face)->readData();
auto data = (const uint32*)cubeTexture.accessStoredMipFace(0, face)->readData();
if (data == nullptr) {
continue;
}
@ -806,29 +813,24 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector<
// index of texel in texture
// get color from texture and map to range [0, 1]
float red { 0.0f };
float green { 0.0f };
float blue { 0.0f };
// get color from texture
glm::vec3 color{ 0.f, 0.f, 0.f };
for (int i = 0; i < stride; ++i) {
for (int j = 0; j < stride; ++j) {
int k = (int)(x + i - halfStride + (y + j - halfStride) * width) * numComponents;
red += ColorUtils::sRGB8ToLinearFloat(data[k + roffset]);
green += ColorUtils::sRGB8ToLinearFloat(data[k + goffset]);
blue += ColorUtils::sRGB8ToLinearFloat(data[k + boffset]);
int k = (int)(x + i - halfStride + (y + j - halfStride) * width);
color += unpackFunc(data[k]);
}
}
glm::vec3 clr(red, green, blue);
// scale color and add to previously accumulated coefficients
// red
sphericalHarmonicsScale(shBuffB.data(), order, shBuff.data(), clr.r * fDiffSolid);
sphericalHarmonicsScale(shBuffB.data(), order, shBuff.data(), color.r * fDiffSolid);
sphericalHarmonicsAdd(resultR.data(), order, resultR.data(), shBuffB.data());
// green
sphericalHarmonicsScale(shBuffB.data(), order, shBuff.data(), clr.g * fDiffSolid);
sphericalHarmonicsScale(shBuffB.data(), order, shBuff.data(), color.g * fDiffSolid);
sphericalHarmonicsAdd(resultG.data(), order, resultG.data(), shBuffB.data());
// blue
sphericalHarmonicsScale(shBuffB.data(), order, shBuff.data(), clr.b * fDiffSolid);
sphericalHarmonicsScale(shBuffB.data(), order, shBuff.data(), color.b * fDiffSolid);
sphericalHarmonicsAdd(resultB.data(), order, resultB.data(), shBuffB.data());
}
}

View file

@ -12,6 +12,7 @@
#include "Image.h"
#include <nvtt/nvtt.h>
#include <glm/gtc/packing.hpp>
#include <QUrl>
#include <QImage>
@ -260,8 +261,6 @@ gpu::TexturePointer processImage(const QByteArray& content, const std::string& f
return texture;
}
QImage processSourceImage(const QImage& srcImage, bool cubemap) {
PROFILE_RANGE(resource_parse, "processSourceImage");
const glm::uvec2 srcImageSize = toGlm(srcImage.size());
@ -331,10 +330,81 @@ struct MyErrorHandler : public nvtt::ErrorHandler {
}
};
void generateMips(gpu::Texture* texture, QImage& image, int face = -1) {
#if CPU_MIPMAPS
PROFILE_RANGE(resource_parse, "generateMips");
void generateHDRMips(gpu::Texture* texture, const QImage& image, int face) {
assert(image.format() == QIMAGE_HDR_FORMAT);
const int width = image.width(), height = image.height();
std::vector<glm::vec4> data;
std::vector<glm::vec4>::iterator dataIt;
data.resize(width*height);
dataIt = data.begin();
for (auto lineNb = 0; lineNb < height; lineNb++) {
const uint32* srcPixelIt = (const uint32*) image.constScanLine(lineNb);
const uint32* srcPixelEnd = srcPixelIt + width;
while (srcPixelIt < srcPixelEnd) {
*dataIt = glm::vec4(glm::unpackF2x11_1x10(*srcPixelIt), 1.f);
++srcPixelIt;
++dataIt;
}
}
assert(dataIt == data.end());
nvtt::TextureType textureType = nvtt::TextureType_2D;
nvtt::InputFormat inputFormat = nvtt::InputFormat_RGBA_32F;
nvtt::WrapMode wrapMode = nvtt::WrapMode_Mirror;
nvtt::RoundMode roundMode = nvtt::RoundMode_None;
nvtt::AlphaMode alphaMode = nvtt::AlphaMode_None;
nvtt::CompressionOptions compressionOptions;
compressionOptions.setQuality(nvtt::Quality_Production);
auto mipFormat = texture->getStoredMipFormat();
if (mipFormat == gpu::Element::COLOR_COMPRESSED_HDR_RGB) {
compressionOptions.setFormat(nvtt::Format_BC6);
}
else if (mipFormat == gpu::Element::COLOR_RGB9E5) {
compressionOptions.setFormat(nvtt::Format_RGB);
compressionOptions.setPixelType(nvtt::PixelType_SharedExp);
compressionOptions.setPitchAlignment(4);
compressionOptions.setPixelFormat(9, 9, 9, 5);
}
else if (mipFormat == gpu::Element::COLOR_R11G11B10) {
// WARNING : With NVTT 2.1, using float 11/10 produces an assertion in the compressor
compressionOptions.setFormat(nvtt::Format_RGB);
compressionOptions.setPixelType(nvtt::PixelType_Float);
compressionOptions.setPitchAlignment(4);
compressionOptions.setPixelFormat(11, 11, 10, 0);
}
else {
qCWarning(imagelogging) << "Unknown mip format";
Q_UNREACHABLE();
return;
}
nvtt::OutputOptions outputOptions;
outputOptions.setOutputHeader(false);
MyOutputHandler outputHandler(texture, face);
outputOptions.setOutputHandler(&outputHandler);
MyErrorHandler errorHandler;
outputOptions.setErrorHandler(&errorHandler);
nvtt::Context context;
int mipLevel = 0;
nvtt::Surface surface;
surface.setImage(inputFormat, width, height, 1, &(*data.begin()));
surface.setAlphaMode(alphaMode);
surface.setWrapMode(wrapMode);
context.compress(surface, face, mipLevel++, compressionOptions, outputOptions);
while (surface.canMakeNextMipmap()) {
surface.buildNextMipmap(nvtt::MipmapFilter_Box);
context.compress(surface, face, mipLevel++, compressionOptions, outputOptions);
}
}
void generateLDRMips(gpu::Texture* texture, QImage& image, int face) {
if (image.format() != QImage::Format_ARGB32) {
image = image.convertToFormat(QImage::Format_ARGB32);
}
@ -388,10 +458,10 @@ void generateMips(gpu::Texture* texture, QImage& image, int face = -1) {
compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm);
compressionOptions.setPitchAlignment(4);
compressionOptions.setPixelFormat(32,
0x000000FF,
0x0000FF00,
0x00FF0000,
0xFF000000);
0x000000FF,
0x0000FF00,
0x00FF0000,
0xFF000000);
inputGamma = 1.0f;
outputGamma = 1.0f;
} else if (mipFormat == gpu::Element::COLOR_BGRA_32) {
@ -399,10 +469,10 @@ void generateMips(gpu::Texture* texture, QImage& image, int face = -1) {
compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm);
compressionOptions.setPitchAlignment(4);
compressionOptions.setPixelFormat(32,
0x00FF0000,
0x0000FF00,
0x000000FF,
0xFF000000);
0x00FF0000,
0x0000FF00,
0x000000FF,
0xFF000000);
inputGamma = 1.0f;
outputGamma = 1.0f;
} else if (mipFormat == gpu::Element::COLOR_SRGBA_32) {
@ -410,19 +480,19 @@ void generateMips(gpu::Texture* texture, QImage& image, int face = -1) {
compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm);
compressionOptions.setPitchAlignment(4);
compressionOptions.setPixelFormat(32,
0x000000FF,
0x0000FF00,
0x00FF0000,
0xFF000000);
0x000000FF,
0x0000FF00,
0x00FF0000,
0xFF000000);
} else if (mipFormat == gpu::Element::COLOR_SBGRA_32) {
compressionOptions.setFormat(nvtt::Format_RGBA);
compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm);
compressionOptions.setPitchAlignment(4);
compressionOptions.setPixelFormat(32,
0x00FF0000,
0x0000FF00,
0x000000FF,
0xFF000000);
0x00FF0000,
0x0000FF00,
0x000000FF,
0xFF000000);
} else if (mipFormat == gpu::Element::COLOR_R_8) {
compressionOptions.setFormat(nvtt::Format_RGB);
compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm);
@ -449,6 +519,17 @@ void generateMips(gpu::Texture* texture, QImage& image, int face = -1) {
nvtt::Compressor compressor;
compressor.process(inputOptions, compressionOptions, outputOptions);
}
void generateMips(gpu::Texture* texture, QImage& image, int face = -1) {
#if CPU_MIPMAPS
PROFILE_RANGE(resource_parse, "generateMips");
if (image.format() == QIMAGE_HDR_FORMAT) {
generateHDRMips(texture, image, face);
} else {
generateLDRMips(texture, image, face);
}
#else
texture->autoGenerateMips(-1);
#endif
@ -926,24 +1007,46 @@ const CubeLayout CubeLayout::CUBEMAP_LAYOUTS[] = {
};
const int CubeLayout::NUM_CUBEMAP_LAYOUTS = sizeof(CubeLayout::CUBEMAP_LAYOUTS) / sizeof(CubeLayout);
QImage convertToHDRFormat(QImage srcImage) {
QImage hdrImage(srcImage.width(), srcImage.height(), (QImage::Format)QIMAGE_HDR_FORMAT);
srcImage = srcImage.convertToFormat(QImage::Format_ARGB32);
for (auto y = 0; y < srcImage.height(); y++) {
const QRgb* srcLineIt = (const QRgb*) srcImage.constScanLine(y);
const QRgb* srcLineEnd = srcLineIt + srcImage.width();
uint32* hdrLineIt = (uint32*) hdrImage.scanLine(y);
glm::vec3 color;
while (srcLineIt < srcLineEnd) {
color.r = qRed(*srcLineIt);
color.g = qGreen(*srcLineIt);
color.b = qBlue(*srcLineIt);
*hdrLineIt = glm::packF2x11_1x10(color / 255.f);
++srcLineIt;
++hdrLineIt;
}
}
return hdrImage;
}
gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(const QImage& srcImage, const std::string& srcImageName, bool generateIrradiance) {
PROFILE_RANGE(resource_parse, "processCubeTextureColorFromImage");
gpu::TexturePointer theTexture = nullptr;
if ((srcImage.width() > 0) && (srcImage.height() > 0)) {
QImage image = processSourceImage(srcImage, true);
if (image.format() != QImage::Format_ARGB32) {
image = image.convertToFormat(QImage::Format_ARGB32);
if (image.format() != QIMAGE_HDR_FORMAT) {
image = convertToHDRFormat(image);
}
gpu::Element formatMip;
gpu::Element formatGPU;
if (isCubeTexturesCompressionEnabled()) {
formatMip = gpu::Element::COLOR_COMPRESSED_SRGBA_HIGH;
formatGPU = gpu::Element::COLOR_COMPRESSED_SRGBA_HIGH;
formatMip = gpu::Element::COLOR_COMPRESSED_HDR_RGB;
formatGPU = gpu::Element::COLOR_COMPRESSED_HDR_RGB;
} else {
formatMip = gpu::Element::COLOR_SRGBA_32;
formatGPU = gpu::Element::COLOR_SRGBA_32;
formatMip = gpu::Element::COLOR_R11G11B10;
formatGPU = gpu::Element::COLOR_R11G11B10;
}
// Find the layout of the cubemap in the 2D image
@ -991,9 +1094,9 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(const QImage&
// Generate irradiance while we are at it
if (generateIrradiance) {
PROFILE_RANGE(resource_parse, "generateIrradiance");
auto irradianceTexture = gpu::Texture::createCube(gpu::Element::COLOR_SRGBA_32, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP));
auto irradianceTexture = gpu::Texture::createCube(gpu::Element::COLOR_R11G11B10, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP));
irradianceTexture->setSource(srcImageName);
irradianceTexture->setStoredMipFormat(gpu::Element::COLOR_SBGRA_32);
irradianceTexture->setStoredMipFormat(gpu::Element::COLOR_R11G11B10);
for (uint8 face = 0; face < faces.size(); ++face) {
irradianceTexture->assignStoredMipFace(0, face, faces[face].byteCount(), faces[face].constBits());
}

View file

@ -13,11 +13,11 @@
#define hifi_image_Image_h
#include <QVariant>
#include <QImage>
#include <gpu/Texture.h>
class QByteArray;
class QImage;
namespace image {
@ -74,6 +74,11 @@ void setNormalTexturesCompressionEnabled(bool enabled);
void setGrayscaleTexturesCompressionEnabled(bool enabled);
void setCubeTexturesCompressionEnabled(bool enabled);
enum
{
QIMAGE_HDR_FORMAT = QImage::Format_RGB30
};
gpu::TexturePointer processImage(const QByteArray& content, const std::string& url, int maxNumPixels, TextureUsage::Type textureType);
} // namespace image