Add ETC support to mipgen / imageio / suzanne.
This shrinks file size as follows, although it seems to darken the rendered result. ``` 1398209 Oct 2 16:22 ao.ktx 699172 Oct 2 16:20 ao_etc.ktx 1398209 Oct 2 16:22 metallic.ktx 699172 Oct 2 16:19 metallic_etc.ktx 1398209 Oct 2 16:22 roughness.ktx 699172 Oct 2 16:20 roughness_etc.ktx ```
This commit is contained in:
@@ -189,6 +189,17 @@ public:
|
||||
static constexpr uint32_t SRGB8_ALPHA8_ASTC_12x10 = 0x93DC;
|
||||
static constexpr uint32_t SRGB8_ALPHA8_ASTC_12x12 = 0x93DD;
|
||||
|
||||
static constexpr uint32_t R11_EAC = 0x9270;
|
||||
static constexpr uint32_t SIGNED_R11_EAC = 0x9271;
|
||||
static constexpr uint32_t RG11_EAC = 0x9272;
|
||||
static constexpr uint32_t SIGNED_RG11_EAC = 0x9273;
|
||||
static constexpr uint32_t RGB8_ETC2 = 0x9274;
|
||||
static constexpr uint32_t SRGB8_ETC2 = 0x9275;
|
||||
static constexpr uint32_t RGB8_ALPHA1_ETC2 = 0x9276;
|
||||
static constexpr uint32_t SRGB8_ALPHA1_ETC = 0x9277;
|
||||
static constexpr uint32_t RGBA8_ETC2_EAC = 0x9278;
|
||||
static constexpr uint32_t SRGB8_ALPHA8_ETC2_EAC = 0x9279;
|
||||
|
||||
private:
|
||||
image::KtxInfo mInfo = {};
|
||||
uint32_t mNumMipLevels;
|
||||
|
||||
@@ -30,35 +30,20 @@
|
||||
|
||||
namespace image {
|
||||
|
||||
// Controls how fast compression occurs at the cost of quality in the resulting image.
|
||||
enum class AstcPreset {
|
||||
VERYFAST,
|
||||
FAST,
|
||||
MEDIUM,
|
||||
THOROUGH,
|
||||
EXHAUSTIVE,
|
||||
};
|
||||
|
||||
// Informs the encoder what texels represent; this is especially crucial for normal maps.
|
||||
enum class AstcSemantic {
|
||||
COLORS_LDR,
|
||||
COLORS_HDR,
|
||||
NORMALS,
|
||||
};
|
||||
|
||||
// The encoder configuration controls the quality and speed of compression, as well as the resulting
|
||||
// format. The specified block size must be one of the 14 block sizes that can be consumed by ES 3.2
|
||||
// as per https://www.khronos.org/registry/OpenGL-Refpages/es3/html/glCompressedTexImage2D.xhtml
|
||||
struct AstcConfig {
|
||||
AstcPreset quality;
|
||||
AstcSemantic semantic;
|
||||
math::ushort2 blocksize;
|
||||
bool srgb;
|
||||
};
|
||||
|
||||
enum class CompressedFormat {
|
||||
INVALID = 0,
|
||||
|
||||
R11_EAC = 0x9270,
|
||||
SIGNED_R11_EAC = 0x9271,
|
||||
RG11_EAC = 0x9272,
|
||||
SIGNED_RG11_EAC = 0x9273,
|
||||
RGB8_ETC2 = 0x9274,
|
||||
SRGB8_ETC2 = 0x9275,
|
||||
RGB8_ALPHA1_ETC2 = 0x9276,
|
||||
SRGB8_ALPHA1_ETC = 0x9277,
|
||||
RGBA8_ETC2_EAC = 0x9278,
|
||||
SRGB8_ALPHA8_ETC2_EAC = 0x9279,
|
||||
|
||||
RGB_S3TC_DXT1 = 0x83F0,
|
||||
RGBA_S3TC_DXT1 = 0x83F1,
|
||||
RGBA_S3TC_DXT3 = 0x83F2,
|
||||
@@ -105,6 +90,34 @@ struct CompressedTexture {
|
||||
std::unique_ptr<uint8_t[]> data;
|
||||
};
|
||||
|
||||
// ASTC ////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Controls how fast compression occurs at the cost of quality in the resulting image.
|
||||
enum class AstcPreset {
|
||||
VERYFAST,
|
||||
FAST,
|
||||
MEDIUM,
|
||||
THOROUGH,
|
||||
EXHAUSTIVE,
|
||||
};
|
||||
|
||||
// Informs the encoder what texels represent; this is especially crucial for normal maps.
|
||||
enum class AstcSemantic {
|
||||
COLORS_LDR,
|
||||
COLORS_HDR,
|
||||
NORMALS,
|
||||
};
|
||||
|
||||
// The encoder configuration controls the quality and speed of compression, as well as the resulting
|
||||
// format. The specified block size must be one of the 14 block sizes that can be consumed by ES 3.2
|
||||
// as per https://www.khronos.org/registry/OpenGL-Refpages/es3/html/glCompressedTexImage2D.xhtml
|
||||
struct AstcConfig {
|
||||
AstcPreset quality;
|
||||
AstcSemantic semantic;
|
||||
math::ushort2 blocksize;
|
||||
bool srgb;
|
||||
};
|
||||
|
||||
// Uses the CPU to compress a linear image (1 to 4 channels) into an ASTC texture. The 16-byte
|
||||
// header block that ARM uses in their file format is not included.
|
||||
CompressedTexture astcCompress(const LinearImage& source, AstcConfig config);
|
||||
@@ -115,6 +128,37 @@ CompressedTexture astcCompress(const LinearImage& source, AstcConfig config);
|
||||
// thorough_normals_6x6, veryfast_hdr_12x10
|
||||
AstcConfig astcParseOptionString(const std::string& options);
|
||||
|
||||
// ETC /////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
enum class EtcErrorMetric {
|
||||
RGBA,
|
||||
RGBX,
|
||||
REC709,
|
||||
NUMERIC,
|
||||
NORMALXYZ,
|
||||
};
|
||||
|
||||
// Informs the ETC encoder of the desired output. Effort sets the quality / speed tradeoff with
|
||||
// a number between 0 and 100.
|
||||
struct EtcConfig {
|
||||
CompressedFormat format;
|
||||
EtcErrorMetric metric;
|
||||
int effort;
|
||||
};
|
||||
|
||||
// Uses the CPU to compress a linear image (1 to 4 channels) into an ETC texture.
|
||||
CompressedTexture etcCompress(const LinearImage& source, EtcConfig config);
|
||||
|
||||
// Converts a string into an ETC compression configuration where the string has the form
|
||||
// FORMAT_METRIC_EFFORT where:
|
||||
// - FORMAT is one of: r11, signed_r11, rg11, signed_rg11, rgb8, srgb8, rgb8_alpha,
|
||||
// srgb8_alpha, rgba8, and srgb8_alpha8
|
||||
// - METRIC is one of: rgba, rgbx, rec709, numeric, and normalxyz
|
||||
// - EFFORT is an integer between 0 and 100
|
||||
EtcConfig etcParseOptionString(const std::string& options);
|
||||
|
||||
// S3TC ////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Informs the S3TC encoder of the desired output.
|
||||
struct S3tcConfig {
|
||||
CompressedFormat format;
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <cmath>
|
||||
|
||||
#include <astcenc.h>
|
||||
#include <Etc.h>
|
||||
|
||||
#define STB_DXT_IMPLEMENTATION
|
||||
#include <stb_dxt.h>
|
||||
@@ -314,6 +315,103 @@ S3tcConfig s3tcParseOptionString(const std::string& options) {
|
||||
return {};
|
||||
}
|
||||
|
||||
CompressedTexture etcCompress(const LinearImage& original, EtcConfig config) {
|
||||
LinearImage source = extendToFourChannels(original);
|
||||
constexpr int threadcount = 1; // TODO: set this thread count
|
||||
Etc::Image::Format etcformat;
|
||||
switch (config.format) {
|
||||
case CompressedFormat::R11_EAC: etcformat = Etc::Image::Format::R11; break;
|
||||
case CompressedFormat::SIGNED_R11_EAC: etcformat = Etc::Image::Format::SIGNED_R11; break;
|
||||
case CompressedFormat::RG11_EAC: etcformat = Etc::Image::Format::RG11; break;
|
||||
case CompressedFormat::SIGNED_RG11_EAC: etcformat = Etc::Image::Format::SIGNED_RG11; break;
|
||||
case CompressedFormat::RGB8_ETC2: etcformat = Etc::Image::Format::RGB8; break;
|
||||
case CompressedFormat::SRGB8_ETC2: etcformat = Etc::Image::Format::SRGB8; break;
|
||||
case CompressedFormat::RGB8_ALPHA1_ETC2: etcformat = Etc::Image::Format::RGB8A1; break;
|
||||
case CompressedFormat::SRGB8_ALPHA1_ETC: etcformat = Etc::Image::Format::SRGB8A1; break;
|
||||
case CompressedFormat::RGBA8_ETC2_EAC: etcformat = Etc::Image::Format::RGBA8; break;
|
||||
case CompressedFormat::SRGB8_ALPHA8_ETC2_EAC: etcformat = Etc::Image::Format::SRGBA8; break;
|
||||
default: return {};
|
||||
}
|
||||
Etc::ErrorMetric etcmetric;
|
||||
switch (config.metric) {
|
||||
case EtcErrorMetric::RGBA: etcmetric = Etc::RGBA; break;
|
||||
case EtcErrorMetric::RGBX: etcmetric = Etc::RGBX; break;
|
||||
case EtcErrorMetric::REC709: etcmetric = Etc::REC709; break;
|
||||
case EtcErrorMetric::NUMERIC: etcmetric = Etc::NUMERIC; break;
|
||||
case EtcErrorMetric::NORMALXYZ: etcmetric = Etc::NORMALXYZ; break;
|
||||
default: return {};
|
||||
}
|
||||
unsigned char *paucEncodingBits;
|
||||
unsigned int uiEncodingBitsBytes;
|
||||
unsigned int uiExtendedWidth;
|
||||
unsigned int uiExtendedHeight;
|
||||
int iEncodingTime_ms;
|
||||
|
||||
// The etc2comp API doesn't tell you that you need to free paucEncodingBits, but they have a
|
||||
// commented-out "delete[] m_paucEncodingBits" in their Image destructor, which is essentially
|
||||
// what our unique_ptr wrapper does (CompressedTexture::data).
|
||||
|
||||
Etc::Encode(source.getPixelRef(0, 0),
|
||||
source.getWidth(), source.getHeight(),
|
||||
etcformat,
|
||||
etcmetric,
|
||||
config.effort,
|
||||
threadcount,
|
||||
1024,
|
||||
&paucEncodingBits, &uiEncodingBitsBytes,
|
||||
&uiExtendedWidth, &uiExtendedHeight,
|
||||
&iEncodingTime_ms);
|
||||
|
||||
return {
|
||||
.format = config.format,
|
||||
.size = uiEncodingBitsBytes,
|
||||
.data = decltype(CompressedTexture::data)(paucEncodingBits)
|
||||
};
|
||||
}
|
||||
|
||||
EtcConfig etcParseOptionString(const std::string& options) {
|
||||
EtcConfig result {};
|
||||
const size_t _2 = options.rfind('_');
|
||||
const size_t _1 = options.rfind('_', _2 - 1);
|
||||
std::string sformat = options.substr(0, _1);
|
||||
std::string smetric = options.substr(_1 + 1, _2 - _1 - 1);
|
||||
std::string seffort = options.substr(_2 + 1);
|
||||
if (sformat == "r11") {
|
||||
result.format = CompressedFormat::R11_EAC;
|
||||
} else if (sformat == "signed_r11") {
|
||||
result.format = CompressedFormat::SIGNED_R11_EAC;
|
||||
} else if (sformat == "rg11") {
|
||||
result.format = CompressedFormat::RG11_EAC;
|
||||
} else if (sformat == "signed_rg11") {
|
||||
result.format = CompressedFormat::SIGNED_RG11_EAC;
|
||||
} else if (sformat == "rgb8") {
|
||||
result.format = CompressedFormat::RGB8_ETC2;
|
||||
} else if (sformat == "srgb8") {
|
||||
result.format = CompressedFormat::SRGB8_ETC2;
|
||||
} else if (sformat == "rgb8_alpha") {
|
||||
result.format = CompressedFormat::RGB8_ALPHA1_ETC2;
|
||||
} else if (sformat == "srgb8_alpha") {
|
||||
result.format = CompressedFormat::SRGB8_ALPHA1_ETC;
|
||||
} else if (sformat == "rgba8_etc2") {
|
||||
result.format = CompressedFormat::RGBA8_ETC2_EAC;
|
||||
} else if (sformat == "srgb8_alpha8_etc2") {
|
||||
result.format = CompressedFormat::SRGB8_ALPHA8_ETC2_EAC;
|
||||
}
|
||||
if (smetric == "rgba") {
|
||||
result.metric = EtcErrorMetric::RGBA;
|
||||
} else if (smetric == "rgbx") {
|
||||
result.metric = EtcErrorMetric::RGBX;
|
||||
} else if (smetric == "rec709") {
|
||||
result.metric = EtcErrorMetric::REC709;
|
||||
} else if (smetric == "numeric") {
|
||||
result.metric = EtcErrorMetric::NUMERIC;
|
||||
} else if (smetric == "normalxyz") {
|
||||
result.metric = EtcErrorMetric::NORMALXYZ;
|
||||
}
|
||||
result.effort = std::stoi(seffort);
|
||||
return result;
|
||||
}
|
||||
|
||||
static LinearImage extendToFourChannels(LinearImage original) {
|
||||
LinearImage source = original;
|
||||
const uint32_t width = source.getWidth();
|
||||
|
||||
@@ -83,13 +83,18 @@ function(add_ktxfiles SOURCE TARGET EXTRA_ARGS)
|
||||
DEPENDS mipgen)
|
||||
endfunction()
|
||||
|
||||
set(ETC_R11_ARGS "--grayscale;--compression=etc_r11_numeric_40")
|
||||
|
||||
add_ktxfiles("assets/models/monkey/albedo.png" "monkey/albedo.ktx" "")
|
||||
add_ktxfiles("assets/models/monkey/albedo.png" "monkey/albedo_astc.ktx" "--compression=astc_fast_ldr_4x4")
|
||||
add_ktxfiles("assets/models/monkey/albedo.png" "monkey/albedo_s3tc.ktx" "--compression=s3tc_rgb_dxt1")
|
||||
add_ktxfiles("assets/models/monkey/normal.png" "monkey/normal.ktx" "--kernel=NORMALS;--linear")
|
||||
add_ktxfiles("assets/models/monkey/roughness.png" "monkey/roughness.ktx" "--grayscale")
|
||||
add_ktxfiles("assets/models/monkey/roughness.png" "monkey/roughness_etc.ktx" "${ETC_R11_ARGS}")
|
||||
add_ktxfiles("assets/models/monkey/metallic.png" "monkey/metallic.ktx" "--grayscale")
|
||||
add_ktxfiles("assets/models/monkey/metallic.png" "monkey/metallic_etc.ktx" "${ETC_R11_ARGS}")
|
||||
add_ktxfiles("assets/models/monkey/ao.png" "monkey/ao.ktx" "--grayscale")
|
||||
add_ktxfiles("assets/models/monkey/ao.png" "monkey/ao_etc.ktx" "${ETC_R11_ARGS}")
|
||||
|
||||
# Convert OBJ files into filamesh files.
|
||||
function(add_mesh SOURCE TARGET)
|
||||
|
||||
@@ -348,6 +348,16 @@ T toFilamentEnum(uint32_t format) {
|
||||
case KtxBundle::SRGB8_ALPHA8_ASTC_10x10: return T::SRGB8_ALPHA8_ASTC_10x10;
|
||||
case KtxBundle::SRGB8_ALPHA8_ASTC_12x10: return T::SRGB8_ALPHA8_ASTC_12x10;
|
||||
case KtxBundle::SRGB8_ALPHA8_ASTC_12x12: return T::SRGB8_ALPHA8_ASTC_12x12;
|
||||
case KtxBundle::R11_EAC: return T::EAC_R11;
|
||||
case KtxBundle::SIGNED_R11_EAC: return T::EAC_R11_SIGNED;
|
||||
case KtxBundle::RG11_EAC: return T::EAC_RG11;
|
||||
case KtxBundle::SIGNED_RG11_EAC: return T::EAC_RG11_SIGNED;
|
||||
case KtxBundle::RGB8_ETC2: return T::ETC2_RGB8;
|
||||
case KtxBundle::SRGB8_ETC2: return T::ETC2_SRGB8;
|
||||
case KtxBundle::RGB8_ALPHA1_ETC2: return T::ETC2_RGB8_A1;
|
||||
case KtxBundle::SRGB8_ALPHA1_ETC: return T::ETC2_SRGB8_A1;
|
||||
case KtxBundle::RGBA8_ETC2_EAC: return T::ETC2_EAC_RGBA8;
|
||||
case KtxBundle::SRGB8_ALPHA8_ETC2_EAC: return T::ETC2_EAC_SRGBA8;
|
||||
}
|
||||
return (T) 0xffff;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ let queued_mouse_events = [];
|
||||
|
||||
let use_astc = false;
|
||||
let use_s3tc = false;
|
||||
let use_etc1 = false;
|
||||
let use_etc = false;
|
||||
|
||||
let canvas = document.getElementById('filament-canvas');
|
||||
let ctx = GL.createContext(canvas, {
|
||||
@@ -19,12 +19,14 @@ let ctx = GL.createContext(canvas, {
|
||||
GL.makeContextCurrent(ctx);
|
||||
for (let ext of Module.ctx.getSupportedExtensions()) {
|
||||
if (ext == "WEBGL_compressed_texture_s3tc") {
|
||||
Module.ctx.getExtension('WEBGL_compressed_texture_s3tc');
|
||||
use_s3tc = true;
|
||||
} else if (ext == "WEBGL_compressed_texture_astc") {
|
||||
Module.ctx.getExtension('WEBGL_compressed_texture_astc');
|
||||
use_astc = true;
|
||||
} else if (ext == "WEBGL_compressed_texture_etc1") {
|
||||
use_etc1 = true;
|
||||
} else if (ext == "WEBGL_compressed_texture_etc") {
|
||||
Module.ctx.getExtension('WEBGL_compressed_texture_etc');
|
||||
use_etc = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -66,14 +66,6 @@ static Texture* setTextureParameter(Engine& engine, filaweb::Asset& asset, strin
|
||||
asset->texture.reset();
|
||||
};
|
||||
|
||||
Texture::Format format;
|
||||
switch (info.glTypeSize) {
|
||||
case 1: format = Texture::Format::R; break;
|
||||
case 2: format = Texture::Format::RG; break;
|
||||
case 3: format = Texture::Format::RGB; break;
|
||||
case 4: format = Texture::Format::RGBA; break;
|
||||
}
|
||||
|
||||
uint8_t* data;
|
||||
uint32_t nbytes;
|
||||
asset.texture->getBlob({}, &data, &nbytes);
|
||||
@@ -97,6 +89,14 @@ static Texture* setTextureParameter(Engine& engine, filaweb::Asset& asset, strin
|
||||
return texture;
|
||||
}
|
||||
|
||||
Texture::Format format;
|
||||
switch (info.glTypeSize) {
|
||||
case 1: format = Texture::Format::R; break;
|
||||
case 2: format = Texture::Format::RG; break;
|
||||
case 3: format = Texture::Format::RGB; break;
|
||||
case 4: format = Texture::Format::RGBA; break;
|
||||
}
|
||||
|
||||
Texture::PixelBufferDescriptor pb(data, nbytes, format, Texture::Type::UBYTE, destructor,
|
||||
&asset);
|
||||
|
||||
|
||||
@@ -22,14 +22,19 @@
|
||||
<script src="suzanne.js"></script>
|
||||
<script src="filaweb.js"></script>
|
||||
<script>
|
||||
|
||||
function load_etcfile(basename) {
|
||||
return use_etc ? load_rawfile(basename + "_etc.ktx") : load_rawfile(basename + ".ktx");
|
||||
}
|
||||
|
||||
load({
|
||||
'albedo': (use_s3tc ? load_rawfile('monkey/albedo_s3tc.ktx') :
|
||||
(use_astc ? load_rawfile('monkey/albedo_astc.ktx') :
|
||||
load_rawfile('monkey/albedo.ktx'))),
|
||||
'metallic': load_rawfile('monkey/metallic.ktx'),
|
||||
'roughness': load_rawfile('monkey/roughness.ktx'),
|
||||
'metallic': load_etcfile('monkey/metallic'),
|
||||
'roughness': load_etcfile('monkey/roughness'),
|
||||
'normal': load_rawfile('monkey/normal.ktx'),
|
||||
'ao': load_rawfile('monkey/ao.ktx'),
|
||||
'ao': load_etcfile('monkey/ao'),
|
||||
'mesh': load_rawfile('monkey/mesh.filamesh'),
|
||||
'syferfontein_18d_clear_2k': load_cubemap('syferfontein_18d_clear_2k', '.ktx')
|
||||
});
|
||||
|
||||
@@ -81,6 +81,11 @@ Options:
|
||||
KTX:
|
||||
astc_[fast|thorough]_[ldr|hdr]_WxH, where WxH is a valid block size
|
||||
st3c_rgb_dxt1, st3c_rgba_dxt5
|
||||
etc_FORMAT_METRIC_EFFORT
|
||||
FORMAT is r11, signed_r11, rg11, signed_rg11, rgb8, srgb8, rgb8_alpha
|
||||
srgb8_alpha, rgba8, or srgb8_alpha8
|
||||
METRIC is rgba, rgbx, rec709, numeric, or normalxyz
|
||||
EFFORT is an integer between 0 and 100
|
||||
PNG: Ignored
|
||||
Radiance: Ignored
|
||||
Photoshop: 16 (default), 32
|
||||
@@ -90,6 +95,7 @@ Options:
|
||||
Examples:
|
||||
MIPGEN -g --kernel=hermite grassland.png mip_%03d.png
|
||||
MIPGEN -f ktx --compression=astc_fast_ldr_4x4 grassland.png mips.ktx
|
||||
MIPGEN -f ktx --compression=etc_rgb_rgba_40 grassland.png mips.ktx
|
||||
)TXT";
|
||||
|
||||
static const char* HTML_PREFIX = R"HTML(<!DOCTYPE html>
|
||||
@@ -278,15 +284,17 @@ int main(int argc, char* argv[]) {
|
||||
}
|
||||
AstcConfig astcConfig {};
|
||||
S3tcConfig s3tcConfig {};
|
||||
EtcConfig etcConfig {};
|
||||
if (!g_compression.empty()) {
|
||||
if (g_compression.substr(0, 5) == "astc_") {
|
||||
string suffix = g_compression.substr(5);
|
||||
astcConfig = astcParseOptionString(suffix);
|
||||
astcConfig = astcParseOptionString(g_compression.substr(5));
|
||||
} else if (g_compression.substr(0, 5) == "s3tc_") {
|
||||
string suffix = g_compression.substr(5);
|
||||
s3tcConfig = s3tcParseOptionString(suffix);
|
||||
s3tcConfig = s3tcParseOptionString(g_compression.substr(5));
|
||||
} else if (g_compression.substr(0, 4) == "etc_") {
|
||||
etcConfig = etcParseOptionString(g_compression.substr(4));
|
||||
}
|
||||
if (astcConfig.blocksize[0] == 0 && s3tcConfig.format == CompressedFormat::INVALID) {
|
||||
if (astcConfig.blocksize[0] == 0 && s3tcConfig.format == CompressedFormat::INVALID
|
||||
&& etcConfig.format == CompressedFormat::INVALID) {
|
||||
cerr << "Unrecognized compression: " << g_compression << endl;
|
||||
return 1;
|
||||
}
|
||||
@@ -322,6 +330,14 @@ int main(int argc, char* argv[]) {
|
||||
info.glInternalFormat = (uint32_t) tex.format;
|
||||
return;
|
||||
}
|
||||
if (etcConfig.format != CompressedFormat::INVALID) {
|
||||
printf("Starting ETC compression for %s (%dx%d)\n", inputPath.getName().c_str(),
|
||||
image.getWidth(), image.getHeight());
|
||||
CompressedTexture tex = etcCompress(image, etcConfig);
|
||||
container.setBlob({mip++}, tex.data.get(), tex.size);
|
||||
info.glInternalFormat = (uint32_t) tex.format;
|
||||
return;
|
||||
}
|
||||
if (g_grayscale && g_linearized) {
|
||||
data = fromLinearToGrayscale<uint8_t>(image);
|
||||
} else if (g_grayscale) {
|
||||
|
||||
Reference in New Issue
Block a user