Compare commits
5 commits
e31d416f94
...
7f0ebc122a
Author | SHA1 | Date | |
---|---|---|---|
7f0ebc122a | |||
e4b918b384 | |||
d9778546b5 | |||
878137db71 | |||
0949e1bf99 |
20 changed files with 11484 additions and 39 deletions
6
.gitmodules
vendored
6
.gitmodules
vendored
|
@ -13,3 +13,9 @@
|
|||
[submodule "libs/nglfw"]
|
||||
path = libs/nglfw
|
||||
url = https://github.com/DiThi/nglfw
|
||||
[submodule "libs/astc/astc-encoder"]
|
||||
path = libs/astc/astc-encoder
|
||||
url = https://github.com/ARM-software/astc-encoder
|
||||
[submodule "libs/bc7enc/bc7enc_rdo"]
|
||||
path = libs/bc7enc/bc7enc_rdo
|
||||
url = https://github.com/DiThi/bc7enc_rdo
|
||||
|
|
1
libs/astc/astc-encoder
Submodule
1
libs/astc/astc-encoder
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 6d5e58f3643abd11e863dd897d576bd16c33d7b1
|
166
libs/astc/astc.nim
Normal file
166
libs/astc/astc.nim
Normal file
|
@ -0,0 +1,166 @@
|
|||
|
||||
# We need this for the extern "C", not for exposing symbols
|
||||
{.passC:"-DASTCENC_DYNAMIC_LIBRARY".}
|
||||
|
||||
{.compile:"astc-encoder/Source/astcenc_averages_and_directions.cpp",
|
||||
compile:"astc-encoder/Source/astcenc_block_sizes.cpp",
|
||||
compile:"astc-encoder/Source/astcenc_color_quantize.cpp",
|
||||
compile:"astc-encoder/Source/astcenc_color_unquantize.cpp",
|
||||
compile:"astc-encoder/Source/astcenc_compress_symbolic.cpp",
|
||||
compile:"astc-encoder/Source/astcenc_compute_variance.cpp",
|
||||
compile:"astc-encoder/Source/astcenc_decompress_symbolic.cpp",
|
||||
compile:"astc-encoder/Source/astcenc_diagnostic_trace.cpp",
|
||||
compile:"astc-encoder/Source/astcenc_entry.cpp",
|
||||
compile:"astc-encoder/Source/astcenc_find_best_partitioning.cpp",
|
||||
compile:"astc-encoder/Source/astcenc_ideal_endpoints_and_weights.cpp",
|
||||
compile:"astc-encoder/Source/astcenc_image.cpp",
|
||||
compile:"astc-encoder/Source/astcenc_integer_sequence.cpp",
|
||||
compile:"astc-encoder/Source/astcenc_mathlib.cpp",
|
||||
compile:"astc-encoder/Source/astcenc_mathlib_softfloat.cpp",
|
||||
compile:"astc-encoder/Source/astcenc_partition_tables.cpp",
|
||||
compile:"astc-encoder/Source/astcenc_percentile_tables.cpp",
|
||||
compile:"astc-encoder/Source/astcenc_pick_best_endpoint_format.cpp",
|
||||
compile:"astc-encoder/Source/astcenc_quantization.cpp",
|
||||
compile:"astc-encoder/Source/astcenc_symbolic_physical.cpp",
|
||||
compile:"astc-encoder/Source/astcenc_weight_align.cpp",
|
||||
compile:"astc-encoder/Source/astcenc_weight_quant_xfer_tables.cpp".}
|
||||
|
||||
type AstcencContext* = object
|
||||
|
||||
type AstcencError* {.size:4.} = enum
|
||||
ASTCENC_SUCCESS = 0, ASTCENC_ERR_OUT_OF_MEM, ASTCENC_ERR_BAD_CPU_FLOAT,
|
||||
ASTCENC_ERR_BAD_PARAM, ASTCENC_ERR_BAD_BLOCK_SIZE, ASTCENC_ERR_BAD_PROFILE,
|
||||
ASTCENC_ERR_BAD_QUALITY, ASTCENC_ERR_BAD_SWIZZLE, ASTCENC_ERR_BAD_FLAGS,
|
||||
ASTCENC_ERR_BAD_CONTEXT, ASTCENC_ERR_NOT_IMPLEMENTED,
|
||||
ASTCENC_ERR_BAD_DECODE_MODE, ASTCENC_ERR_DTRACE_FAILURE
|
||||
|
||||
template astc_assert*(err: AstcencError) =
|
||||
assert err == ASTCENC_SUCCESS, "ASTC error: " & $err
|
||||
|
||||
type AstcencProfile* {.size:4.} = enum
|
||||
ASTCENC_PRF_LDR_SRGB = 0, ASTCENC_PRF_LDR, ASTCENC_PRF_HDR_RGB_LDR_A,
|
||||
ASTCENC_PRF_HDR
|
||||
|
||||
let ASTCENC_PRE_FASTEST*: float32 = 0.0f
|
||||
let ASTCENC_PRE_FAST*: float32 = 10.0f
|
||||
let ASTCENC_PRE_MEDIUM*: float32 = 60.0f
|
||||
let ASTCENC_PRE_THOROUGH*: float32 = 98.0f
|
||||
let ASTCENC_PRE_VERYTHOROUGH*: float32 = 99.0f
|
||||
let ASTCENC_PRE_EXHAUSTIVE*: float32 = 100.0f
|
||||
|
||||
type AstcencSwz* {.size:4.} = enum
|
||||
SWZ_R = 0, SWZ_G = 1, SWZ_B = 2, SWZ_A = 3,
|
||||
SWZ_0 = 4, SWZ_1 = 5, SWZ_Z = 6
|
||||
|
||||
type AstcencSwizzle* {.bycopy.} = object
|
||||
r*: AstcencSwz
|
||||
g*: AstcencSwz
|
||||
b*: AstcencSwz
|
||||
a*: AstcencSwz
|
||||
|
||||
type AstcencType* {.size:4.} = enum
|
||||
ASTCENC_TYPE_U8 = 0, ASTCENC_TYPE_F16 = 1, ASTCENC_TYPE_F32 = 2
|
||||
|
||||
type AstcencProgressCallback* = proc(p: float32) {.cdecl.}
|
||||
|
||||
let ASTCENC_FLG_MAP_NORMAL*: uint32 = 1 shl 0
|
||||
let ASTCENC_FLG_USE_DECODE_UNORM8*: uint32 = 1 shl 1
|
||||
let ASTCENC_FLG_USE_ALPHA_WEIGHT*: uint32 = 1 shl 2
|
||||
let ASTCENC_FLG_USE_PERCEPTUAL*: uint32 = 1 shl 3
|
||||
let ASTCENC_FLG_DECOMPRESS_ONLY*: uint32 = 1 shl 4
|
||||
let ASTCENC_FLG_SELF_DECOMPRESS_ONLY*: uint32 = 1 shl 5
|
||||
let ASTCENC_FLG_MAP_RGBM*: uint32 = 1 shl 6
|
||||
let ASTCENC_ALL_FLAGS*: uint32 = ASTCENC_FLG_MAP_NORMAL or ASTCENC_FLG_MAP_RGBM or
|
||||
ASTCENC_FLG_USE_ALPHA_WEIGHT or ASTCENC_FLG_USE_PERCEPTUAL or
|
||||
ASTCENC_FLG_USE_DECODE_UNORM8 or ASTCENC_FLG_DECOMPRESS_ONLY or
|
||||
ASTCENC_FLG_SELF_DECOMPRESS_ONLY
|
||||
|
||||
type AstcencConfig* {.bycopy.} = object
|
||||
profile*: AstcencProfile
|
||||
flags*: uint32
|
||||
block_x*: uint32
|
||||
block_y*: uint32
|
||||
block_z*: uint32
|
||||
cw_r_weight*: float32
|
||||
cw_g_weight*: float32
|
||||
cw_b_weight*: float32
|
||||
cw_a_weight*: float32
|
||||
a_scale_radius*: uint32
|
||||
rgbm_m_scale*: float32
|
||||
tune_partition_count_limit*: uint32
|
||||
tune_2partition_index_limit*: uint32
|
||||
tune_3partition_index_limit*: uint32
|
||||
tune_4partition_index_limit*: uint32
|
||||
tune_block_mode_limit*: uint32
|
||||
tune_refinement_limit*: uint32
|
||||
tune_candidate_limit*: uint32
|
||||
tune_2partitioning_candidate_limit*: uint32
|
||||
tune_3partitioning_candidate_limit*: uint32
|
||||
tune_4partitioning_candidate_limit*: uint32
|
||||
tune_db_limit*: float32
|
||||
tune_mse_overshoot*: float32
|
||||
tune_2partition_early_out_limit_factor*: float32
|
||||
tune_3partition_early_out_limit_factor*: float32
|
||||
tune_2plane_early_out_limit_correlation*: float32
|
||||
tune_search_mode0_enable*: float32
|
||||
progress_callback*: AstcencProgressCallback
|
||||
when defined(ASTCENC_DIAGNOSTICS):
|
||||
trace_file_path*: cstring
|
||||
|
||||
type AstcencImage* {.bycopy.} = object
|
||||
dim_x*: uint32
|
||||
dim_y*: uint32
|
||||
dim_z*: uint32
|
||||
data_type*: AstcencType
|
||||
data*: ptr pointer
|
||||
|
||||
type AstcencBlockInfo* {.bycopy.} = object
|
||||
profile*: AstcencProfile
|
||||
block_x*: uint32
|
||||
block_y*: uint32
|
||||
block_z*: uint32
|
||||
texel_count*: uint32
|
||||
is_error_block*: bool
|
||||
is_constant_block*: bool
|
||||
is_hdr_block*: bool
|
||||
is_dual_plane_block*: bool
|
||||
partition_count*: uint32
|
||||
partition_index*: uint32
|
||||
dual_plane_component*: uint32
|
||||
color_endpoint_modes*: array[4, uint32]
|
||||
color_level_count*: uint32
|
||||
weight_level_count*: uint32
|
||||
weight_x*: uint32
|
||||
weight_y*: uint32
|
||||
weight_z*: uint32
|
||||
color_endpoints*: array[4, array[2, array[4, float32]]]
|
||||
weight_values_plane1*: array[216, float32]
|
||||
weight_values_plane2*: array[216, float32]
|
||||
partition_assignment*: array[216, uint8]
|
||||
|
||||
proc astcenc_config_init*(profile: AstcencProfile; block_x: uint32; block_y: uint32;
|
||||
block_z: uint32; quality: float32; flags: uint32;
|
||||
config: ptr AstcencConfig): AstcencError {.cdecl,
|
||||
importc: "astcenc_config_init".}
|
||||
proc astcenc_context_alloc*(config: ptr AstcencConfig; thread_count: uint32;
|
||||
context: var ptr AstcencContext): AstcencError {.cdecl,
|
||||
importc: "astcenc_context_alloc".}
|
||||
proc astcenc_compress_image*(context: ptr AstcencContext; image: ptr AstcencImage;
|
||||
swizzle: ptr AstcencSwizzle; data_out: ptr uint8;
|
||||
data_len: csize_t; thread_index: uint32): AstcencError {.
|
||||
cdecl, importc: "astcenc_compress_image".}
|
||||
proc astcenc_compress_reset*(context: ptr AstcencContext): AstcencError {.cdecl,
|
||||
importc: "astcenc_compress_reset".}
|
||||
proc astcenc_decompress_image*(context: ptr AstcencContext; data: ptr uint8;
|
||||
data_len: csize_t; image_out: ptr AstcencImage;
|
||||
swizzle: ptr AstcencSwizzle; thread_index: uint32): AstcencError {.
|
||||
cdecl, importc: "astcenc_decompress_image".}
|
||||
proc astcenc_decompress_reset*(context: ptr AstcencContext): AstcencError {.cdecl,
|
||||
importc: "astcenc_decompress_reset".}
|
||||
proc astcenc_context_free*(context: ptr AstcencContext) {.cdecl,
|
||||
importc: "astcenc_context_free".}
|
||||
proc astcenc_get_block_info*(context: ptr AstcencContext; data: array[16, uint8];
|
||||
info: ptr AstcencBlockInfo): AstcencError {.cdecl,
|
||||
importc: "astcenc_get_block_info".}
|
||||
proc astcenc_get_error_string*(status: AstcencError): cstring {.cdecl,
|
||||
importc: "astcenc_get_error_string".}
|
160
libs/bc7enc/bc7enc.cpp
Normal file
160
libs/bc7enc/bc7enc.cpp
Normal file
|
@ -0,0 +1,160 @@
|
|||
#define BC7ENC_VERSION "1.08"
|
||||
#define COMPUTE_SSIM (0)
|
||||
#if _OPENMP
|
||||
#include <omp.h>
|
||||
#endif
|
||||
#include <cstdint>
|
||||
#include "bc7enc_rdo/rdo_bc_encoder.h"
|
||||
#include "bc7enc_rdo/utils.h"
|
||||
|
||||
// This is based on main() in test.cpp to make a C function
|
||||
|
||||
// Valid formats: 1 3 4 5 7
|
||||
// BC1 RGB 4bpp fast, small
|
||||
// BC3 RGBA 8bpp fast
|
||||
// BC4 gray 4bpp best for grayscale
|
||||
// BC5 X+Y 8bpp
|
||||
// BC7 RGB(A) 8bpp slow, best quality
|
||||
|
||||
typedef struct {
|
||||
void *data;
|
||||
int32_t len;
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
int8_t format; // BCn format to use.
|
||||
// TODO: encoding settings
|
||||
} EncodeBcInput;
|
||||
|
||||
typedef struct {
|
||||
void *data;
|
||||
int32_t len;
|
||||
int32_t row_len;
|
||||
} EncodeBcOutput;
|
||||
|
||||
typedef enum {
|
||||
NO_ERROR = 0,
|
||||
ENCODER_INIT_ERROR = 1,
|
||||
ENCODE_ERROR = 2,
|
||||
INVALID_SIZE_ERROR = 3,
|
||||
UNSUPPORTED_FORMAT_ERROR = 4,
|
||||
INVALID_INPUT_LENGTH_ERROR = 5,
|
||||
INVALID_OUTPUT_LENGTH_ERROR = 6,
|
||||
} EncodeBcError;
|
||||
|
||||
extern "C" EncodeBcError encode_bc(EncodeBcInput &input, EncodeBcOutput &output, bool verbose)
|
||||
{
|
||||
bool quiet_mode = !verbose;
|
||||
|
||||
int max_threads = 1;
|
||||
#if _OPENMP
|
||||
max_threads = std::min(std::max(1, omp_get_max_threads()), 128);
|
||||
#endif
|
||||
|
||||
uint32_t pixel_format_bpp = 8;
|
||||
|
||||
rdo_bc::rdo_bc_params rp;
|
||||
rp.m_rdo_max_threads = max_threads;
|
||||
rp.m_status_output = !quiet_mode;
|
||||
|
||||
switch(input.format){
|
||||
case 1:
|
||||
rp.m_dxgi_format = DXGI_FORMAT_BC1_UNORM;
|
||||
pixel_format_bpp = 4;
|
||||
break;
|
||||
case 3:
|
||||
rp.m_dxgi_format = DXGI_FORMAT_BC3_UNORM;
|
||||
break;
|
||||
case 4:
|
||||
rp.m_dxgi_format = DXGI_FORMAT_BC4_UNORM;
|
||||
pixel_format_bpp = 4;
|
||||
break;
|
||||
case 5:
|
||||
rp.m_dxgi_format = DXGI_FORMAT_BC5_UNORM;
|
||||
break;
|
||||
case 7:
|
||||
// it's already default
|
||||
break;
|
||||
default:
|
||||
return UNSUPPORTED_FORMAT_ERROR;
|
||||
}
|
||||
int32_t width = input.width;
|
||||
int32_t height = input.height;
|
||||
if(width == 0 || height == 0){
|
||||
return INVALID_SIZE_ERROR;
|
||||
}
|
||||
|
||||
utils::image_u8 source_image;
|
||||
|
||||
// TODO: avoid a copy somehow
|
||||
source_image.init(width, height);
|
||||
int32_t input_data_size = width * height * sizeof(uint32_t);
|
||||
if(input_data_size != input.len){
|
||||
return INVALID_INPUT_LENGTH_ERROR;
|
||||
}
|
||||
memcpy(source_image.get_pixels().data(), input.data, input.len);
|
||||
|
||||
if (rp.m_status_output)
|
||||
{
|
||||
printf("Max threads: %u\n", max_threads);
|
||||
printf("Supports bc7e.ispc: %u\n", SUPPORT_BC7E);
|
||||
}
|
||||
|
||||
clock_t overall_start_t = clock();
|
||||
|
||||
rdo_bc::rdo_bc_encoder encoder;
|
||||
if (!encoder.init(source_image, rp))
|
||||
{
|
||||
fprintf(stderr, "rdo_bc_encoder::init() failed!\n");
|
||||
return ENCODER_INIT_ERROR;
|
||||
}
|
||||
|
||||
if (rp.m_status_output)
|
||||
{
|
||||
if (encoder.get_has_alpha())
|
||||
printf("Source image has an alpha channel.\n");
|
||||
else
|
||||
printf("Source image is opaque.\n");
|
||||
}
|
||||
|
||||
if (!encoder.encode())
|
||||
{
|
||||
fprintf(stderr, "rdo_bc_encoder::encode() failed!\n");
|
||||
return ENCODE_ERROR;
|
||||
}
|
||||
|
||||
clock_t overall_end_t = clock();
|
||||
|
||||
if (rp.m_status_output)
|
||||
printf("Total processing time: %f secs\n", (double)(overall_end_t - overall_start_t) / CLOCKS_PER_SEC);
|
||||
|
||||
// Compress the output data losslessly using Deflate
|
||||
const uint32_t output_data_size = encoder.get_total_blocks_size_in_bytes();
|
||||
// const uint32_t pre_rdo_comp_size = get_deflate_size(encoder.get_prerdo_blocks(), output_data_size);
|
||||
|
||||
// float pre_rdo_lz_bits_per_texel = (pre_rdo_comp_size * 8.0f) / encoder.get_total_texels();
|
||||
|
||||
// if (rp.m_status_output)
|
||||
// {
|
||||
// printf("Output data size: %u, LZ (Deflate) compressed file size: %u, %3.2f bits/texel\n",
|
||||
// output_data_size,
|
||||
// (uint32_t)pre_rdo_comp_size,
|
||||
// pre_rdo_lz_bits_per_texel);
|
||||
// }
|
||||
|
||||
// const uint32_t comp_size = get_deflate_size(encoder.get_blocks(), output_data_size);
|
||||
|
||||
// float lz_bits_per_texel = comp_size * 8.0f / encoder.get_total_texels();
|
||||
|
||||
// if (rp.m_status_output)
|
||||
// printf("RDO output data size: %u, LZ (Deflate) compressed file size: %u, %3.2f bits/texel, savings: %3.2f%%\n", output_data_size, (uint32_t)comp_size, lz_bits_per_texel,
|
||||
// (lz_bits_per_texel != pre_rdo_lz_bits_per_texel) ? 100.0f - (lz_bits_per_texel * 100.0f) / pre_rdo_lz_bits_per_texel : 0.0f);
|
||||
|
||||
if(output_data_size != output.len){
|
||||
fprintf(stderr, "Output length is %d, expected %d\n", output.len, output_data_size);
|
||||
return INVALID_OUTPUT_LENGTH_ERROR;
|
||||
}
|
||||
// TODO: avoid a copy
|
||||
memcpy(output.data, encoder.get_blocks(), output_data_size);
|
||||
|
||||
return NO_ERROR;
|
||||
}
|
88
libs/bc7enc/bc7enc.nim
Normal file
88
libs/bc7enc/bc7enc.nim
Normal file
|
@ -0,0 +1,88 @@
|
|||
|
||||
{.passC:"-DBC7ENC_USE_MINIZ=0".}
|
||||
{.compile:"./bc7enc_rdo/bc7decomp.cpp",
|
||||
compile:"./bc7enc_rdo/bc7decomp_ref.cpp",
|
||||
compile:"./bc7enc_rdo/bc7enc.cpp",
|
||||
compile:"./bc7enc_rdo/ert.cpp",
|
||||
compile:"./bc7enc_rdo/lodepng.cpp",
|
||||
compile:"./bc7enc_rdo/rdo_bc_encoder.cpp",
|
||||
compile:"./bc7enc_rdo/rgbcx.cpp",
|
||||
compile:"./bc7enc_rdo/utils.cpp",
|
||||
compile:"./bc7enc.cpp".}
|
||||
|
||||
## Valid formats: 1 3 4 5 7
|
||||
## BC1 RGB 4bpp fast, small
|
||||
## BC3 RGBA 8bpp fast
|
||||
## BC4 gray 4bpp best for grayscale
|
||||
## BC5 X+Y 8bpp
|
||||
## BC7 RGB(A) 8bpp slow, best quality
|
||||
|
||||
type EncodeBcInput* {.bycopy.} = object
|
||||
data*: pointer
|
||||
len*: int32
|
||||
width*: int32
|
||||
height*: int32
|
||||
format*: int8 ## BCn format to use.
|
||||
## TODO: encoding settings
|
||||
|
||||
type EncodeBcOutput* {.bycopy.} = object
|
||||
data*: pointer
|
||||
len*: int32
|
||||
row_len*: int32
|
||||
|
||||
type EncodeBcError* {.size:4.} = enum
|
||||
NO_ERROR = 0, ENCODER_INIT_ERROR = 1, ENCODE_ERROR = 2,
|
||||
INVALID_SIZE_ERROR = 3, UNSUPPORTED_FORMAT_ERROR = 4,
|
||||
INVALID_INPUT_LENGTH_ERROR = 5, INVALID_OUTPUT_LENGTH_ERROR = 6
|
||||
|
||||
proc encode_bc*(input: var EncodeBcInput, output: var EncodeBcOutput, verbose: bool): EncodeBcError {.importc, cdecl.}
|
||||
|
||||
{.hint[XDeclaredButNotUsed]: off.}
|
||||
|
||||
# S3TC (BC1-3)
|
||||
const GL_COMPRESSED_RGB_S3TC_DXT1_EXT = 0x83F0'i32
|
||||
const GL_COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1'i32
|
||||
const GL_COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2'i32
|
||||
const GL_COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3'i32
|
||||
# S3TC (BC1-3) sRGB
|
||||
const GL_COMPRESSED_SRGB_S3TC_DXT1_EXT = 0x8C4C'i32
|
||||
const GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT = 0x8C4D'i32
|
||||
const GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT = 0x8C4E'i32
|
||||
const GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT = 0x8C4F'i32
|
||||
# RGTC (BC4-5)
|
||||
const GL_COMPRESSED_RED_RGTC1_EXT = 0x8DBB'i32
|
||||
const GL_COMPRESSED_SIGNED_RED_RGTC1_EXT = 0x8DBC'i32
|
||||
const GL_COMPRESSED_RED_GREEN_RGTC2_EXT = 0x8DBD'i32
|
||||
const GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT = 0x8DBE'i32
|
||||
# BPTC float (BC6H)
|
||||
const GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB = 0x8E8E'i32
|
||||
const GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB = 0x8E8F'i32
|
||||
# BPTC (BC7)
|
||||
const GL_COMPRESSED_RGBA_BPTC_UNORM_ARB = 0x8E8C'i32
|
||||
# BPTC (BC7) sRGB
|
||||
const GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB = 0x8E8D'i32
|
||||
|
||||
func get_bc_bpp_internal_format*(bc_format: int8, is_sRGB: bool): (int8, int32) =
|
||||
let (bpp, internal_format) = if is_sRGB:
|
||||
case bc_format:
|
||||
of 1: (4, GL_COMPRESSED_SRGB_S3TC_DXT1_EXT)
|
||||
of 2: (8, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT)
|
||||
of 3: (8, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT)
|
||||
of 4: (4, GL_COMPRESSED_RED_RGTC1_EXT) # sRGB is ignored
|
||||
of 5: (8, GL_COMPRESSED_RED_GREEN_RGTC2_EXT) # sRGB is ignored
|
||||
of 6: (8, GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB) # sRGB is ignored
|
||||
of 7: (8, GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB)
|
||||
else: (0, 0'i32)
|
||||
else:
|
||||
case bc_format:
|
||||
of 1: (4, GL_COMPRESSED_RGB_S3TC_DXT1_EXT)
|
||||
of 2: (8, GL_COMPRESSED_RGBA_S3TC_DXT3_EXT)
|
||||
of 3: (8, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT)
|
||||
of 4: (4, GL_COMPRESSED_RED_RGTC1_EXT)
|
||||
of 5: (8, GL_COMPRESSED_RED_GREEN_RGTC2_EXT)
|
||||
of 6: (8, GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB)
|
||||
of 7: (8, GL_COMPRESSED_RGBA_BPTC_UNORM_ARB)
|
||||
else: (0, 0'i32)
|
||||
assert bpp != 0, "Invalid bc_format " & $bc_format
|
||||
return (bpp.int8, internal_format)
|
||||
|
1
libs/bc7enc/bc7enc_rdo
Submodule
1
libs/bc7enc/bc7enc_rdo
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit f3941ed03e771c5006bb43ebff10e669156c858a
|
|
@ -12,6 +12,7 @@ type KtxInfo* = object
|
|||
type KtxPart* = object
|
||||
width*, height*: int32
|
||||
layer*, face*, mip_level*: int32
|
||||
slice*: int32
|
||||
data*: pointer
|
||||
len*: int
|
||||
row_len*: int
|
||||
|
@ -249,7 +250,7 @@ proc ParseDdsKtx*(p: pointer, len: int): seq[KtxPart] =
|
|||
let err_msg = $cast[cstring](err.msg.addr)
|
||||
raise ValueError.newException err_msg
|
||||
|
||||
func get_ASTC_internal_format*(blk_size: (int,int), is_sRGB: bool): int32 =
|
||||
func get_ASTC_internal_format*(blk_size: (SomeInteger,SomeInteger), is_sRGB: bool): int32 =
|
||||
result = if is_sRGB:
|
||||
case blk_size[0]*100 or blk_size[1]:
|
||||
of 04_04: GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR
|
||||
|
|
63
libs/stb_image_resize/stb_image_resize.nim
Normal file
63
libs/stb_image_resize/stb_image_resize.nim
Normal file
|
@ -0,0 +1,63 @@
|
|||
|
||||
{.compile:"stb_image_resize2.cpp".}
|
||||
|
||||
type StbirPixelLayout* {.size:4.} = enum
|
||||
StbirBgr = 0 # 3-chan, with order specified (for channel flipping)
|
||||
Stbir1channel = 1
|
||||
Stbir2channel = 2
|
||||
StbirRgb = 3 # 3-chan, with order specified (for channel flipping)
|
||||
StbirRgba = 4
|
||||
Stbir4channel = 5
|
||||
|
||||
# StbirRgba = 4 # alpha formats, where alpha is NOT premultiplied into color channels
|
||||
StbirBgra = 6
|
||||
StbirArgb = 7
|
||||
StbirAbgr = 8
|
||||
StbirRa = 9
|
||||
StbirAr = 10
|
||||
|
||||
StbirRgbaPm = 11 # alpha formats, where alpha is premultiplied into color channels
|
||||
StbirBgraPm = 12
|
||||
StbirArgbPm = 13
|
||||
StbirAbgrPm = 14
|
||||
StbirRaPm = 15
|
||||
StbirArPm = 16
|
||||
|
||||
# StbirRgbaNoAw = 11 # alpha formats, where NO alpha weighting is applied at all!
|
||||
# StbirBgraNoAw = 12 # these are just synonyms for the _PM flags (which also do
|
||||
# StbirArgbNoAw = 13 # no alpha weighting). These names just make it more clear
|
||||
# StbirAbgrNoAw = 14 # for some folks).
|
||||
# StbirRaNoAw = 15
|
||||
# StbirArNoAw = 16
|
||||
|
||||
type StbirEdge* {.size:4.} = enum
|
||||
STBIR_EDGE_CLAMP = 0,
|
||||
STBIR_EDGE_REFLECT = 1,
|
||||
STBIR_EDGE_WRAP = 2, # this edge mode is slower and uses more memory
|
||||
STBIR_EDGE_ZERO = 3,
|
||||
|
||||
type StbirFilter* {.size:4.} = enum
|
||||
STBIR_FILTER_DEFAULT = 0, # use same filter type that easy-to-use API chooses
|
||||
STBIR_FILTER_BOX = 1, # A trapezoid w/1-pixel wide ramps, same result as box for integer scale ratios
|
||||
STBIR_FILTER_TRIANGLE = 2, # On upsampling, produces same results as bilinear texture filtering
|
||||
STBIR_FILTER_CUBICBSPLINE = 3, # The cubic b-spline (aka Mitchell-Netrevalli with B=1,C=0), gaussian-esque
|
||||
STBIR_FILTER_CATMULLROM = 4, # An interpolating cubic spline
|
||||
STBIR_FILTER_MITCHELL = 5, # Mitchell-Netrevalli filter with B=1/3, C=1/3
|
||||
STBIR_FILTER_POINT_SAMPLE = 6, # Simple point sampling
|
||||
STBIR_FILTER_OTHER = 7, # User callback specified
|
||||
|
||||
type StbirDatatype* {.size:4.} = enum
|
||||
STBIR_TYPE_UINT8 = 0,
|
||||
STBIR_TYPE_UINT8_SRGB = 1,
|
||||
STBIR_TYPE_UINT8_SRGB_ALPHA = 2, # alpha channel, when present, should also be SRGB (this is very unusual)
|
||||
STBIR_TYPE_UINT16 = 3,
|
||||
STBIR_TYPE_FLOAT = 4,
|
||||
STBIR_TYPE_HALF_FLOAT = 5
|
||||
|
||||
proc stbir_resize*(input_pixels: pointer; input_w, input_h, input_stride_in_bytes: int32;
|
||||
output_pixels: pointer; output_w, output_h, output_stride_in_bytes: int32;
|
||||
pixel_layout: StbirPixelLayout, data_type: StbirDatatype,
|
||||
edge: StbirEdge, filter: StbirFilter) {.importc,cdecl.}
|
||||
|
||||
|
||||
|
2
libs/stb_image_resize/stb_image_resize2.cpp
Normal file
2
libs/stb_image_resize/stb_image_resize2.cpp
Normal file
|
@ -0,0 +1,2 @@
|
|||
#define STB_IMAGE_RESIZE_IMPLEMENTATION
|
||||
#include "stb_image_resize2.h"
|
10572
libs/stb_image_resize/stb_image_resize2.h
Normal file
10572
libs/stb_image_resize/stb_image_resize2.h
Normal file
File diff suppressed because it is too large
Load diff
|
@ -33,7 +33,7 @@
|
|||
import ../types
|
||||
|
||||
# Forward declarations
|
||||
proc swap_lines(p: pointer, line_stride, line_count: int)
|
||||
proc swap_lines(p: pointer, line_stride, line_count: int) {.gcsafe.}
|
||||
# End forward declarations
|
||||
|
||||
import std/strformat
|
||||
|
@ -78,6 +78,26 @@ proc channel_count*(format: TextureFormat): int =
|
|||
of SRGB_u8, RGB_u8, RGB_u16, RGB_f16, RGB_f32: 3
|
||||
else: 4
|
||||
|
||||
func component_type*(format: TextureFormat): DataType =
|
||||
case format:
|
||||
of SRGB_u8, SRGB_Alpha_u8, R_u8, RG_u8, RGB_u8, RGBA_u8: UByte
|
||||
of R_f16, RG_f16, RGB_f16, RGBA_f16: HalfFloat
|
||||
of R_f32, RG_f32, RGB_f32, RGBA_f32, Depth_f32: Float
|
||||
of R_u16, RG_u16, RGB_u16, RGBA_u16, Depth_u16: UShort
|
||||
of Depth_u24: UInt
|
||||
|
||||
func resize*(format: TextureFormat, channel_count: int): TextureFormat =
|
||||
if format in [SRGB_u8, SRGB_Alpha_u8] and channel_count < 3:
|
||||
raise ValueError.newException "Can't resize sRGB to " & $channel_count
|
||||
if format in [Depth_u16, Depth_u24, Depth_f32]:
|
||||
raise ValueError.newException "Can't resize depth format"
|
||||
let f = format.int
|
||||
TextureFormat(f - format.channel_count + channel_count)
|
||||
|
||||
func format_depth*(tex: Texture): int {.inline.} =
|
||||
if tex.tex_type == TexCube: 1
|
||||
else: tex.depth
|
||||
|
||||
template toOpenArrayByte(p: pointer, a,b: untyped): untyped =
|
||||
cast[ptr UncheckedArray[byte]](p).toOpenArray(a,b)
|
||||
|
||||
|
@ -88,7 +108,7 @@ proc f32_to_f16(source: ptr UncheckedArray[float32], dest: ptr UncheckedArray[Fl
|
|||
for i in 0 ..< len:
|
||||
dest[i] = source[i].tofloat16(clamp=true)
|
||||
|
||||
proc getDimensionsFormat*(p: pointer, len: int): (int, int, TextureFormat) =
|
||||
proc getDimensionsFormat*(p: pointer, len: int, min_channels=0): (int, int, TextureFormat) =
|
||||
when not defined(nimdoc):
|
||||
if isEXR(p, len):
|
||||
let dims = getEXRDimensions(p, len)
|
||||
|
@ -97,9 +117,12 @@ proc getDimensionsFormat*(p: pointer, len: int): (int, int, TextureFormat) =
|
|||
let dims = decodeImageDimensions(p, len)
|
||||
return (dims.width, dims.height, RGBA_u8)
|
||||
else:
|
||||
# TODO, IMPORTANT: 2 channels for stb_image means grey+alpha
|
||||
# We should handle those cases properly
|
||||
var width, height, channels = 0
|
||||
if not infoFromMemory(p.toOpenArrayByte(0, len-1), width, height, channels):
|
||||
raise ValueError.newException "Could not read image"
|
||||
channels = max(min_channels, channels)
|
||||
let hdr = isHDRFromMemory(p.toOpenArrayByte(0, len-1))
|
||||
let is16 = is16BitFromMemory(p.toOpenArrayByte(0, len-1))
|
||||
# Calculate format with channels, and whether it's hdr or 16 bit
|
||||
|
@ -110,11 +133,16 @@ proc getDimensionsFormat*(p: pointer, len: int): (int, int, TextureFormat) =
|
|||
return (width, height, format)
|
||||
|
||||
proc loadFileFromSlices*(tex: Texture, slices: seq[SliceMem[byte]],
|
||||
callback: proc(tex: Texture, data: SliceMem[byte]), flip = true) =
|
||||
callback: proc(tex: Texture, data: SliceMem[byte]),
|
||||
flip = true, min_channels = 0) {.gcsafe.} =
|
||||
|
||||
when not defined(nimdoc):
|
||||
assert tex.tex_type != TexCube, "Loading a cube texture from file is not supported yet"
|
||||
let layer_stride = tex.width * tex.height * tex.format.stride
|
||||
let format = if min_channels == 0:
|
||||
tex.format
|
||||
else:
|
||||
tex.format.resize(min_channels)
|
||||
let layer_stride = tex.width * tex.height * format.stride
|
||||
var multilayer_buffer: ArrRef[byte]
|
||||
assert tex.depth == slices.len
|
||||
if tex.depth > 1:
|
||||
|
@ -152,7 +180,7 @@ proc loadFileFromSlices*(tex: Texture, slices: seq[SliceMem[byte]],
|
|||
flip = false
|
||||
var w,h,c = 0
|
||||
if isHDRFromMemory(slice.toOpenArrayByte):
|
||||
image_f = loadFFromMemory(slice.toOpenArrayByte, w,h,c,0)
|
||||
image_f = loadFFromMemory(slice.toOpenArrayByte, w,h,c, min_channels)
|
||||
pixels_ptr = image_f.data
|
||||
pixels_len = image_f.byteLen
|
||||
when myouConvertHdrToFloat16:
|
||||
|
@ -162,17 +190,17 @@ proc loadFileFromSlices*(tex: Texture, slices: seq[SliceMem[byte]],
|
|||
image_f.len)
|
||||
pixels_len = pixels_len div 2
|
||||
elif is16BitFromMemory(slice.toOpenArrayByte):
|
||||
image_16 = load16FromMemory(slice.toOpenArrayByte, w,h,c,0)
|
||||
image_16 = load16FromMemory(slice.toOpenArrayByte, w,h,c, min_channels)
|
||||
pixels_ptr = image_16.data
|
||||
pixels_len = image_16.byteLen
|
||||
else:
|
||||
image = loadFromMemory(slice.toOpenArrayByte, w,h,c,0)
|
||||
image = loadFromMemory(slice.toOpenArrayByte, w,h,c, min_channels)
|
||||
pixels_ptr = image.data
|
||||
pixels_len = image.len
|
||||
assert layer_stride == pixels_len,
|
||||
&"Image '{tex.name}' has a length of {pixels_len}, expected {layer_stride}"
|
||||
if flip:
|
||||
swap_lines(pixels_ptr, tex.width * tex.format.stride, tex.height)
|
||||
swap_lines(pixels_ptr, tex.width * format.stride, tex.height)
|
||||
if tex.depth == 1:
|
||||
callback(tex, newSliceMem(pixels_ptr, pixels_len, destructor))
|
||||
return
|
||||
|
@ -181,7 +209,7 @@ proc loadFileFromSlices*(tex: Texture, slices: seq[SliceMem[byte]],
|
|||
callback(tex, multilayer_buffer.toSliceMem)
|
||||
|
||||
|
||||
proc swap_lines(p: pointer, line_stride, line_count: int) =
|
||||
proc swap_lines(p: pointer, line_stride, line_count: int) {.gcsafe.} =
|
||||
template `+`(p: pointer, i: Natural): pointer = cast[pointer](cast[int](p) +% cast[int](i))
|
||||
template `-`(p: pointer, i: Natural): pointer = cast[pointer](cast[int](p) -% cast[int](i))
|
||||
let int_stride = line_stride div sizeof(int)
|
||||
|
@ -201,3 +229,5 @@ proc swap_lines(p: pointer, line_stride, line_count: int) =
|
|||
swap(b1[j], b2[j])
|
||||
p1 = p1 + line_stride
|
||||
p2 = p2 - line_stride
|
||||
|
||||
|
||||
|
|
328
src/gpu_formats/texture_optimize.nim
Normal file
328
src/gpu_formats/texture_optimize.nim
Normal file
|
@ -0,0 +1,328 @@
|
|||
|
||||
import ../types
|
||||
import ./texture_decode
|
||||
from dds_ktx import KtxInfo, KtxPart, get_ASTC_internal_format
|
||||
import arr_ref
|
||||
import stb_image_resize
|
||||
import std/monotimes
|
||||
import std/bitops
|
||||
|
||||
# TODO: don't import it here
|
||||
from ../platform/gl import nil
|
||||
|
||||
when defined(myouUseBC7Encoder):
|
||||
import bc7enc
|
||||
when defined(myouUseAstcEncoder):
|
||||
import astc
|
||||
when defined(android) or defined(ios) or defined(emscripten):
|
||||
template has_bptc_support: bool = gl.GLAD_GL_EXT_texture_compression_bptc
|
||||
else:
|
||||
template has_bptc_support: bool = gl.GLAD_GL_ARB_texture_compression_bptc
|
||||
template has_astc_support: bool = gl.GLAD_GL_OES_texture_compression_astc or
|
||||
gl.GLAD_GL_KHR_texture_compression_astc_ldr
|
||||
|
||||
const myouEngineNumTextureThreads {.intdefine.} = 4
|
||||
const myouEngineCompressTextures {.booldefine.} = true
|
||||
const myouBC7VerboseMode {.booldefine.} = true
|
||||
|
||||
template u32(x: untyped): uint32 = cast[uint32](x)
|
||||
|
||||
type CallbackUncompressed = proc(tex: Texture, data: SliceMem[byte]) {.gcsafe.}
|
||||
type CallbackCompressed = proc(tex: Texture, info: KtxInfo, data: seq[KtxPart], refdata: seq[ArrRef[byte]]) {.gcsafe.}
|
||||
type CompressMipmapResult = tuple[data: ArrRef[byte], row_len: int]
|
||||
type CompressMipmap = proc(width, height: int32, p: pointer, len: int32): CompressMipmapResult {.closure.}
|
||||
|
||||
template block_file_size(width, height, depth, block_x, block_y, block_z, block_byte_size: untyped): int =
|
||||
let blocks_x = (width.int + block_x.int - 1) div block_x.int
|
||||
let blocks_y = (height.int + block_y.int - 1) div block_y.int
|
||||
let blocks_z = (depth.int + block_z.int - 1) div block_z.int
|
||||
blocks_x * blocks_y * blocks_z * block_byte_size.int
|
||||
|
||||
proc make_mipmaps(tex: Texture, pixels: SliceMem[byte], compress: CompressMipmap): (seq[KtxPart], seq[ArrRef[byte]]) =
|
||||
var width = tex.width.int32
|
||||
var height = tex.height.int32
|
||||
let depth = tex.format_depth
|
||||
assert depth == 1, "Compressed 2D arrays and 3D textures not supported yet"
|
||||
assert tex.tex_type != TexCube, "Compressed cube textures not supported yet"
|
||||
let stbir_data_type = case tex.format.component_type:
|
||||
of UByte:
|
||||
if tex.is_sRGB: STBIR_TYPE_UINT8_SRGB
|
||||
else: STBIR_TYPE_UINT8
|
||||
of HalfFloat: STBIR_TYPE_HALF_FLOAT
|
||||
of Float: STBIR_TYPE_FLOAT
|
||||
of UShort: STBIR_TYPE_UINT16
|
||||
else: raise Defect.newException "unreachable"
|
||||
# 4 because we're forcing 4 channels for the encoder
|
||||
let pixel_stride = 4 * (tex.format.stride div tex.format.channel_count)
|
||||
let time = getmonotime().ticks.float/1000000000
|
||||
var parts: seq[KtxPart]
|
||||
var data_refs: seq[ArrRef[byte]]
|
||||
var mip_level = 0'i32
|
||||
var w, prev_w = width
|
||||
var h, prev_h = height
|
||||
# round down to POT (TODO: make configurable)
|
||||
w = 1'i32 shl fastLog2(prev_w)
|
||||
h = 1'i32 shl fastLog2(prev_h)
|
||||
let resize_level_0 = w != prev_w or h != prev_h
|
||||
var current_level, last_level: ArrRef[byte]
|
||||
var last_src: pointer = pixels.data
|
||||
var last_len: int
|
||||
while w >= 4 or h >= 4 or parts.len == 0:
|
||||
(last_src, last_len) = if mip_level != 0 or resize_level_0:
|
||||
current_level = newArrRef[byte](w*h*pixel_stride)
|
||||
stbir_resize(
|
||||
last_src, prev_w, prev_h, 0,
|
||||
current_level.toPointer, w, h, 0,
|
||||
StbirRgba, stbir_data_type,
|
||||
STBIR_EDGE_CLAMP, # TODO: handle other cases
|
||||
STBIR_FILTER_BOX,
|
||||
)
|
||||
last_level = current_level
|
||||
(current_level.toPointer, current_level.byte_len.int)
|
||||
else:
|
||||
(pixels.toPointer, pixels.byte_len)
|
||||
|
||||
let (data, row_len) = compress(w, h, last_src, last_len.int32)
|
||||
|
||||
parts.add KtxPart(
|
||||
width: w, height: h, data: data.toPointer,
|
||||
len: data.byte_len, mip_level: mip_level,
|
||||
row_len: row_len,
|
||||
)
|
||||
data_refs.add data
|
||||
prev_w = w; prev_h = h
|
||||
w = max(1, w shr 1)
|
||||
h = max(1, h shr 1)
|
||||
mip_level.inc
|
||||
let time2 = getmonotime().ticks.float/1000000000
|
||||
echo "time: ", time2-time, " ", tex.name
|
||||
return (parts, data_refs)
|
||||
|
||||
when defined(myouUseBC7Encoder):
|
||||
proc bcn_compress*(tex: Texture, pixels: SliceMem[byte], callback: CallbackCompressed, bc_format: int8) =
|
||||
when not defined(android):
|
||||
let (bpp, internal_format) = get_bc_bpp_internal_format(bc_format, tex.is_sRGB)
|
||||
let block_size_bytes = (4*4*bpp.int) div 8
|
||||
proc compress(w, h: int32, p: pointer, len: int32): CompressMipmapResult {.closure.} =
|
||||
var input = EncodeBcInput(
|
||||
data: p, len: len,
|
||||
width: w, height: h,
|
||||
format: bc_format,
|
||||
)
|
||||
let out_len = block_file_size(w,h,1, 4,4,1, block_size_bytes)
|
||||
let row_len = block_file_size(w,1,1, 4,4,1, block_size_bytes)
|
||||
var data = newArrRef[byte](out_len)
|
||||
var output = EncodeBcOutput(
|
||||
data: data.toPointer,
|
||||
len: out_len.int32,
|
||||
)
|
||||
let err = encode_bc(input, output, myouBC7VerboseMode)
|
||||
assert err == NO_ERROR, "bc7enc error: " & $err
|
||||
return (data, row_len)
|
||||
|
||||
let (parts, data_refs) = make_mipmaps(tex, pixels, compress)
|
||||
|
||||
let info = KtxInfo(
|
||||
width: parts[0].width, height: parts[0].height, depth: tex.format_depth.int32,
|
||||
num_layers: 1, num_mipmaps: parts.len.int32, has_alpha: bc_format in [2,3,7],
|
||||
is_sRGB: tex.is_sRGB, is_bc: true,
|
||||
internal_format: internal_format,
|
||||
)
|
||||
callback(tex, info, parts, data_refs)
|
||||
|
||||
when defined(myouUseAstcEncoder):
|
||||
proc atsc_compress*(tex: Texture, pixels: SliceMem[byte], callback: CallbackCompressed, blk_size = (6,6), quality = 10.0) =
|
||||
let profile = case tex.format.component_type:
|
||||
of UByte:
|
||||
if tex.is_sRGB: ASTCENC_PRF_LDR_SRGB
|
||||
else: ASTCENC_PRF_LDR
|
||||
of HalfFloat: ASTCENC_PRF_HDR_RGB_LDR_A
|
||||
of Float: ASTCENC_PRF_HDR_RGB_LDR_A
|
||||
of UShort: ASTCENC_PRF_HDR_RGB_LDR_A
|
||||
else: raise Defect.newException "unreachable"
|
||||
let data_type = case tex.format:
|
||||
of SRGB_u8, SRGB_Alpha_u8, R_u8, RG_u8, RGB_u8, RGBA_u8: ASTCENC_TYPE_U8
|
||||
of R_f16, RG_f16, RGB_f16, RGBA_f16: ASTCENC_TYPE_F16
|
||||
of R_f32, RG_f32, RGB_f32, RGBA_f32: ASTCENC_TYPE_F32
|
||||
else: raise ValueError.newException "Unsupported data type of " & $tex.format
|
||||
let swizzle = case tex.format.channel_count:
|
||||
# TODO: more optimal usage for 1/2 channels, normals, etc.
|
||||
of 1: AstcencSwizzle(r: SWZ_R, g: SWZ_R, b: SWZ_R, a: SWZ_1)
|
||||
of 2: AstcencSwizzle(r: SWZ_R, g: SWZ_G, b: SWZ_0, a: SWZ_1)
|
||||
of 3: AstcencSwizzle(r: SWZ_R, g: SWZ_G, b: SWZ_B, a: SWZ_1)
|
||||
else: AstcencSwizzle(r: SWZ_R, g: SWZ_G, b: SWZ_B, a: SWZ_A)
|
||||
let flags = 0'u32
|
||||
var config: AstcencConfig
|
||||
# TODO: 3D texture block size
|
||||
astc_assert astcenc_config_init(profile,
|
||||
blk_size[0].uint32, blk_size[1].uint32, 1'u32,
|
||||
quality, flags, config.addr)
|
||||
# config.progress_callback = proc(p: float32) {.cdecl.} =
|
||||
# echo "progress ", p
|
||||
var ctx: ptr AstcencContext
|
||||
astc_assert astcenc_context_alloc(config.addr, 1, ctx)
|
||||
let time = getmonotime().ticks.float/1000000000
|
||||
proc compress(w, h: int32, p: pointer, len: int32): CompressMipmapResult {.closure.} =
|
||||
let buffer_size = block_file_size(w, h, 1,
|
||||
config.block_x, config.block_y, config.block_z, 16)
|
||||
let row_len = block_file_size(w, 1, 1,
|
||||
config.block_x, config.block_y, config.block_z, 16)
|
||||
let data = newArrRef[byte](buffer_size)
|
||||
let pointers = [p]
|
||||
let img = AstcencImage(
|
||||
dim_x: w.u32, dim_y: h.u32, dim_z: 1.u32,
|
||||
data_type: data_type,
|
||||
data: pointers[0].addr,
|
||||
)
|
||||
const thread_index = 0 # TODO
|
||||
let err = astcenc_compress_image(ctx, img.addr, swizzle.addr,
|
||||
data[0].addr, buffer_size.csize_t, thread_index)
|
||||
assert err == ASTCENC_SUCCESS, "ASTC encoding error: " & $err
|
||||
return (data, row_len)
|
||||
|
||||
let (parts, data_refs) = make_mipmaps(tex, pixels, compress)
|
||||
|
||||
let info = KtxInfo(
|
||||
width: parts[0].width, height: parts[0].height, depth: tex.format_depth.int32,
|
||||
num_layers: 1, num_mipmaps: parts.len.int32, has_alpha: tex.format.channel_count == 4,
|
||||
is_sRGB: tex.is_sRGB, is_astc: true,
|
||||
internal_format: get_ASTC_internal_format(blk_size, tex.is_sRGB)
|
||||
)
|
||||
callback(tex, info, parts, data_refs)
|
||||
astc_assert astcenc_compress_reset(ctx)
|
||||
astcenc_context_free(ctx)
|
||||
|
||||
proc loadOptimized*(tex: Texture, slices: seq[SliceMem[byte]],
|
||||
callback_uncompressed: CallbackUncompressed = nil,
|
||||
callback_compressed: CallbackCompressed = nil,
|
||||
flip = true, min_channels = 0) {.gcsafe.} =
|
||||
|
||||
var min_channels = min_channels
|
||||
var will_compress = myouEngineCompressTextures
|
||||
var will_load_uncompressed_first = false
|
||||
var will_encode_all = false
|
||||
|
||||
will_compress = will_compress and
|
||||
callback_compressed != nil and
|
||||
tex.format.component_type != UShort
|
||||
|
||||
if has_bptc_support:
|
||||
# TODO: BC6H or RGBM
|
||||
will_compress = will_compress and
|
||||
tex.format.component_type == UByte
|
||||
elif has_astc_support:
|
||||
# TODO: detect HDR support
|
||||
discard
|
||||
elif will_encode_all:
|
||||
will_load_uncompressed_first = true
|
||||
else:
|
||||
will_compress = false
|
||||
|
||||
if will_compress:
|
||||
min_channels = 4
|
||||
|
||||
tex.loadFileFromSlices(slices, proc(tex: Texture, pixels: SliceMem[byte]) {.gcsafe.} =
|
||||
if callback_uncompressed != nil and
|
||||
(will_load_uncompressed_first or not will_compress):
|
||||
callback_uncompressed(tex, pixels)
|
||||
if will_compress:
|
||||
when defined(myouUseBC7Encoder):
|
||||
let bc_format = if tex.format.channel_count == 1:
|
||||
4.int8 # BC4 is 0.5 bytes per pixel grayscale
|
||||
else:
|
||||
7.int8 # BC7 is just the best at anything else
|
||||
if has_bptc_support:
|
||||
bcn_compress(tex, pixels, callback_compressed, bc_format)
|
||||
if not will_encode_all: return
|
||||
elif will_encode_all:
|
||||
# TODO: callback to store result
|
||||
discard
|
||||
# bcn_compress(tex, pixels, ..., bc_format)
|
||||
when defined(myouUseAstcEncoder):
|
||||
if has_astc_support or will_encode_all:
|
||||
atsc_compress(tex, pixels, callback_compressed)
|
||||
if not will_encode_all: return
|
||||
elif will_encode_all:
|
||||
# TODO: callback to store result
|
||||
discard
|
||||
# atsc_compress(tex, pixels, ...)
|
||||
|
||||
, flip = flip, min_channels = min_channels)
|
||||
|
||||
|
||||
# main -> thread channels
|
||||
type DecodeChanMsg = tuple[
|
||||
tex: Texture, slices: seq[SliceMem[byte]],
|
||||
callback_uncompressed: CallbackUncompressed,
|
||||
callback_compressed: CallbackCompressed,
|
||||
flip: bool, min_channels: int,
|
||||
]
|
||||
var decode_chan: Channel[DecodeChanMsg]
|
||||
# main <- thread channels
|
||||
type DecodeReturnChanMsg = tuple[
|
||||
callback: CallbackUncompressed,
|
||||
tex: Texture, data: SliceMem[byte],
|
||||
]
|
||||
var decode_return_chan: Channel[DecodeReturnChanMsg]
|
||||
type CompressedReturnChanMsg = tuple[
|
||||
callback: CallbackCompressed,
|
||||
tex: Texture, info: KtxInfo, data: seq[KtxPart], refdata: seq[ArrRef[byte]],
|
||||
]
|
||||
var compressed_return_chan: Channel[CompressedReturnChanMsg]
|
||||
|
||||
proc loadOptimizedThreaded*(tex: Texture, slices: seq[SliceMem[byte]],
|
||||
callback_uncompressed: CallbackUncompressed = nil,
|
||||
callback_compressed: CallbackCompressed = nil,
|
||||
flip = true, min_channels = 0) =
|
||||
|
||||
when false:
|
||||
loadOptimized(tex, slices, callback_uncompressed, callback_compressed, flip, min_channels)
|
||||
else:
|
||||
decode_chan.send((tex: tex, slices: slices,
|
||||
callback_uncompressed: callback_uncompressed,
|
||||
callback_compressed: callback_compressed,
|
||||
flip: flip, min_channels: min_channels))
|
||||
|
||||
var workers = newSeq[Thread[void]](myouEngineNumTextureThreads)
|
||||
proc workerThreadProc() {.thread.} =
|
||||
# TODO: handle errors
|
||||
while true:
|
||||
let to_decode = decode_chan.recv()
|
||||
if to_decode.tex == nil:
|
||||
break
|
||||
proc cb(tex: Texture, data: SliceMem[byte]) =
|
||||
decode_return_chan.send((callback: to_decode.callback_uncompressed, tex: tex, data: data))
|
||||
proc cbc(tex: Texture, info: KtxInfo, data: seq[KtxPart], refdata: seq[ArrRef[byte]]) =
|
||||
compressed_return_chan.send((callback: to_decode.callback_compressed, tex: tex,
|
||||
info: info, data: data, refdata: refdata))
|
||||
let cb_out = if to_decode.callback_uncompressed != nil: cb else: nil
|
||||
let cbc_out = if to_decode.callback_compressed != nil: cbc else: nil
|
||||
loadOptimized(to_decode.tex, to_decode.slices, cb_out, cbc_out,
|
||||
to_decode.flip, to_decode.min_channels)
|
||||
|
||||
decode_chan.open()
|
||||
decode_return_chan.open()
|
||||
compressed_return_chan.open()
|
||||
for worker in workers.mitems:
|
||||
worker.createThread(workerThreadProc)
|
||||
|
||||
proc updateTextureWorkerThreads*() =
|
||||
# TODO: handle errors
|
||||
while true:
|
||||
let tried = decode_return_chan.tryRecv()
|
||||
if not tried.dataAvailable:
|
||||
break
|
||||
let (cb, tex, data) = tried.msg
|
||||
cb(tex, data)
|
||||
while true:
|
||||
let tried = compressed_return_chan.tryRecv()
|
||||
if not tried.dataAvailable:
|
||||
break
|
||||
let (cb, tex, info, data, refdata) = tried.msg
|
||||
cb(tex, info, data, refdata)
|
||||
|
||||
proc terminateTextureWorkerThreads*() =
|
||||
for worker in workers:
|
||||
decode_chan.send(DecodeChanMsg.default)
|
||||
for worker in workers:
|
||||
worker.joinThread()
|
|
@ -35,7 +35,6 @@ import vmath except Quat
|
|||
import arr_ref
|
||||
# import tinyre
|
||||
import std/tables
|
||||
import ../gpu_formats/texture_decode
|
||||
import ../platform/gl
|
||||
|
||||
# Forward declarations
|
||||
|
@ -50,7 +49,7 @@ proc unbind*(texture: Texture)
|
|||
proc unbindAllTextures*()
|
||||
proc destroy*(texture: Texture)
|
||||
proc loadFromPixelsPointer*(self: Texture, pixels: pointer)
|
||||
proc loadFromPixels*[T](self: Texture, pixels: SliceMem[T])
|
||||
proc loadFromPixels*[T](self: Texture, pixels: SliceMem[T]) {.gcsafe.}
|
||||
proc loadCubeSideFromPixels*(self: Texture, pixels: pointer, side: int32 = 0)
|
||||
proc setFilter*(self: Texture, filter: TextureFilter)
|
||||
proc newTexture*(engine: MyouEngine, name: string, width, height: int, depth: int = 1,
|
||||
|
@ -68,7 +67,7 @@ proc newTexture*(engine: MyouEngine, name: string, file_name: string, is_sRGB: b
|
|||
): Texture
|
||||
proc setExtrapolation*(self: Texture, ext: TextureExtrapolation)
|
||||
proc getTexturePixels*(self: Texture): TexturePixels
|
||||
proc setMipmapRange*(self: Texture, first = 0, last = 1000)
|
||||
proc setMipmapRange*(self: Texture, first = 0, last = 1000) {.gcsafe.}
|
||||
func vec3size*(self: Texture, mip_level = -1): Vec3
|
||||
func toInternalFormat*(format: TextureFormat): GLenum
|
||||
# End forward declarations
|
||||
|
@ -78,18 +77,23 @@ func toInternalFormat*(format: TextureFormat): GLenum
|
|||
import std/bitops
|
||||
import std/strformat
|
||||
import loadable
|
||||
import ddx_ktx
|
||||
import dds_ktx
|
||||
import ../gpu_formats/texture_decode
|
||||
import ../gpu_formats/texture_optimize
|
||||
|
||||
# TODO: use and test destructor
|
||||
|
||||
# NOTE: we moved this from the render manager to global state
|
||||
# (if we ever need multiple instances with different context
|
||||
# we should move them back)
|
||||
var bound_textures: seq[Texture]
|
||||
var active_texture: int32 = -1
|
||||
var next_texture: int32
|
||||
var max_textures: int32
|
||||
var reserved: int32
|
||||
# NOTE: set as threadvar for optimization (and for having procs gcsafe
|
||||
# implicitely), we only use them in the main thread
|
||||
var bound_textures {.threadvar.}: seq[Texture]
|
||||
var active_texture {.threadvar.}: int32
|
||||
var next_texture {.threadvar.}: int32
|
||||
var max_textures {.threadvar.}: int32
|
||||
var reserved {.threadvar.}: int32
|
||||
active_texture = -1
|
||||
|
||||
if defined(android):
|
||||
# I don't know why slot 0 makes the mesh not render at all
|
||||
|
@ -345,7 +349,7 @@ proc loadFromPixelsPointer*(self: Texture, pixels: pointer) =
|
|||
if self.needsMipmap:
|
||||
glGenerateMipmap(ts.target)
|
||||
|
||||
proc loadFromPixels*[T](self: Texture, pixels: SliceMem[T]) =
|
||||
proc loadFromPixels*[T](self: Texture, pixels: SliceMem[T]) {.gcsafe.} =
|
||||
self.loadFromPixelsPointer(pixels.data)
|
||||
|
||||
proc loadCubeSideFromPixels*(self: Texture, pixels: pointer, side: int32 = 0) =
|
||||
|
@ -358,7 +362,7 @@ proc loadCubeSideFromPixels*(self: Texture, pixels: pointer, side: int32 = 0) =
|
|||
self.width.GLsizei, self.height.GLsizei,
|
||||
0, ts.format, ts.gltype, pixels)
|
||||
|
||||
proc loadCompressedData*(self: Texture, info: KtxInfo, data: seq[KtxPart]) =
|
||||
proc loadCompressedData*(self: Texture, info: KtxInfo, data: seq[KtxPart], refdata: seq[ArrRef[byte]]) {.gcsafe.} =
|
||||
assert info.depth == 1 and info.num_layers == 1,
|
||||
"Compressed array and 3D textures not supported yet"
|
||||
let ts = self.storage
|
||||
|
@ -368,8 +372,9 @@ proc loadCompressedData*(self: Texture, info: KtxInfo, data: seq[KtxPart]) =
|
|||
else: ts.target.GLuint.int32
|
||||
for part in data:
|
||||
glCompressedTexImage2D(cast[GLenum](target+part.face), part.mip_level,
|
||||
info.internal_format.GLenum, self.width.GLsizei, self.height.GLsizei,
|
||||
info.internal_format.GLenum, part.width.GLsizei, part.height.GLsizei,
|
||||
0, part.len.GLsizei, part.data)
|
||||
self.setMipmapRange(0, info.num_mipmaps - 1)
|
||||
|
||||
proc setFilter*(self: Texture, filter: TextureFilter) =
|
||||
self.filter = filter
|
||||
|
@ -441,14 +446,17 @@ func to_sRGB*(format: TextureFormat): TextureFormat =
|
|||
else: raise newException(ValueError, "There's no sRGB version of " & $format)
|
||||
|
||||
proc newTexture*(engine: MyouEngine, name: string, data: SliceMem[byte], is_sRGB: bool, filter: TextureFilter = Trilinear, depth=1, flip=true): Texture =
|
||||
# TODO: replace this function by one taking a LoadableResource
|
||||
var (width, height, format) = getDimensionsFormat(data.data, data.byte_len)
|
||||
if is_sRGB:
|
||||
format = format.to_sRGB
|
||||
if format in [RGB_u8, RGBA_u8]:
|
||||
format = format.to_sRGB
|
||||
else:
|
||||
# TODO: sRGB shader for 1 and 2 channel textures
|
||||
echo &"WARNING: Texture {name} is sRGB but has format {format} _"
|
||||
let self = engine.newTexture(name, width, height, depth, format, filter=filter)
|
||||
self.is_sRGB = is_sRGB
|
||||
engine.renderer.enqueue proc() =
|
||||
self.loadFileFromSlices(@[data], loadFromPixels, flip=flip)
|
||||
self.loaded = true
|
||||
self.loadOptimizedThreaded(@[data], loadFromPixels, loadCompressedData)
|
||||
return self
|
||||
|
||||
proc newTexture*(engine: MyouEngine, name: string, file_name: string, is_sRGB: bool,
|
||||
|
@ -460,6 +468,7 @@ proc newTexture*(engine: MyouEngine, name: string, file_name: string, is_sRGB: b
|
|||
# TODO: Api that stores the LoadableResource so it can be re-loaded later
|
||||
# note: format does not matter at this point, will be replaced later
|
||||
let self = engine.newTexture(name, 0, 0, filter=filter, format=RGBA_u8)
|
||||
self.is_sRGB = is_sRGB
|
||||
var res: LoadableResource
|
||||
proc load(ok: bool, err: string, data: SliceMem[byte]) =
|
||||
assert ok, &"Error loading texture '{self.name}' from '{file_name}': {err}"
|
||||
|
@ -476,8 +485,7 @@ proc newTexture*(engine: MyouEngine, name: string, file_name: string, is_sRGB: b
|
|||
engine.renderer.enqueue proc()=
|
||||
try:
|
||||
self.ensure_storage()
|
||||
self.loadCompressedData(ktx_info, ParseDdsKtx(data.data, data.byte_len))
|
||||
self.setMipmapRange(0, ktx_info.num_mipmaps - 1)
|
||||
self.loadCompressedData(ktx_info, ParseDdsKtx(data.data, data.byte_len), @[])
|
||||
self.loaded = true
|
||||
# except Exception as e:
|
||||
except:
|
||||
|
@ -487,17 +495,20 @@ proc newTexture*(engine: MyouEngine, name: string, file_name: string, is_sRGB: b
|
|||
echo getCurrentExceptionMsg()
|
||||
echo "Error loading texture " & file_name
|
||||
else:
|
||||
var (width, height, format) = getDimensionsFormat(data.data, data.byte_len)
|
||||
var (width, height, format) = getDimensionsFormat(data.data, data.byte_len, 3)
|
||||
if is_sRGB:
|
||||
format = format.to_sRGB
|
||||
if format in [RGB_u8, RGBA_u8]:
|
||||
format = format.to_sRGB
|
||||
else:
|
||||
# TODO: sRGB shader for 1 and 2 channel textures
|
||||
echo &"WARNING: Texture {self.name} is sRGB but has format {format}"
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.format = format
|
||||
engine.renderer.enqueue proc()=
|
||||
try:
|
||||
self.ensure_storage()
|
||||
self.loadFileFromSlices(@[data], loadFromPixels)
|
||||
self.loaded = true
|
||||
self.loadOptimizedThreaded(@[data], loadFromPixels, loadCompressedData)
|
||||
except:
|
||||
# TODO: use logging
|
||||
echo getCurrentExceptionMsg()
|
||||
|
@ -542,7 +553,7 @@ proc getTexturePixels*(self: Texture): TexturePixels =
|
|||
# TODO: limit the amount of mip maps to save some draw calls
|
||||
# (e.g. minimum resolution 16x16 for blurred mipmaps)
|
||||
|
||||
proc setMipmapRange*(self: Texture, first = 0, last = 1000) =
|
||||
proc setMipmapRange*(self: Texture, first = 0, last = 1000) {.gcsafe.} =
|
||||
self.bind_it(needs_active_texture=true)
|
||||
self.mipmap_range = (first, last)
|
||||
glTexParameteri(self.storage.target.GLenum, GL_TEXTURE_BASE_LEVEL, first.GLint);
|
||||
|
|
|
@ -78,6 +78,7 @@ import ./platform/platform
|
|||
import ./loaders/blend
|
||||
import ./util
|
||||
from loadable import updateLoadableWorkerThreads
|
||||
from ./gpu_formats/texture_optimize import updateTextureWorkerThreads
|
||||
import arr_ref
|
||||
|
||||
export arr_ref
|
||||
|
@ -166,6 +167,7 @@ proc myou_main_loop*(self: MyouEngine) =
|
|||
##
|
||||
## You usually don't need to call this. Use `run <#run,MyouEngine>`_
|
||||
## instead.
|
||||
updateTextureWorkerThreads()
|
||||
updateLoadableWorkerThreads()
|
||||
# TODO: make a table object that can be iterated while changing, e.g. with a
|
||||
# seq and a dirty flag to update the seq
|
||||
|
|
|
@ -103,6 +103,10 @@ proc init_graphics*(engine: MyouEngine, width, height: int32, title: string,
|
|||
let rev = opengl_version mod 10
|
||||
assert major >= 3
|
||||
|
||||
if not gladLoadGLES2(nil):
|
||||
echo "Could not initialize OpenGL"
|
||||
quit -1
|
||||
|
||||
engine.renderer.enqueue proc()=
|
||||
when not defined(release) and not defined(emscripten):
|
||||
proc f(source: GLenum, etype: GLenum, id: GLuint, severity: GLenum, length: GLsizei, message: cstring, userParam: pointer) {.stdcall.} =
|
||||
|
|
|
@ -16237,8 +16237,8 @@ proc findCoreGL(glVersion: string) =
|
|||
version = version.replace(p)
|
||||
break
|
||||
|
||||
var major = ord(glVersion[0]) - ord('0')
|
||||
var minor = ord(glVersion[2]) - ord('0')
|
||||
var major = ord(version[0]) - ord('0')
|
||||
var minor = ord(version[2]) - ord('0')
|
||||
|
||||
glVersionMajor = major
|
||||
glVersionMinor = minor
|
||||
|
@ -17861,8 +17861,8 @@ proc findCoreGLES2(glVersion: string) =
|
|||
version = version.replace(p)
|
||||
break
|
||||
|
||||
var major = ord(glVersion[0]) - ord('0')
|
||||
var minor = ord(glVersion[2]) - ord('0')
|
||||
var major = ord(version[0]) - ord('0')
|
||||
var minor = ord(version[2]) - ord('0')
|
||||
|
||||
glVersionMajor = major
|
||||
glVersionMinor = minor
|
||||
|
|
|
@ -5705,8 +5705,8 @@ proc findCoreGLES2(glVersion: string) =
|
|||
version = version.replace(p)
|
||||
break
|
||||
|
||||
var major = ord(glVersion[0]) - ord('0')
|
||||
var minor = ord(glVersion[2]) - ord('0')
|
||||
var major = ord(version[0]) - ord('0')
|
||||
var minor = ord(version[2]) - ord('0')
|
||||
|
||||
glVersionMajor = major
|
||||
glVersionMinor = minor
|
||||
|
|
|
@ -227,10 +227,14 @@ proc init_graphics*(engine: MyouEngine, width, height: int32, title: string,
|
|||
window.glfmSetSurfaceCreatedFunc proc(window: Window, w,h: cint) {.cdecl.} =
|
||||
# force resize
|
||||
window.screen = window.screen
|
||||
if not gladLoadGLES2(nil):
|
||||
echo "Could not initialize OpenGL"
|
||||
quit -1
|
||||
window.screen.engine.renderer.initialize()
|
||||
|
||||
window.glfmSetSurfaceDestroyedFunc proc(window: Window) {.cdecl.} =
|
||||
window.screen.engine.renderer.uninitialize()
|
||||
|
||||
|
||||
when not defined(release) and not defined(emscripten):
|
||||
proc f(source: GLenum, etype: GLenum, id: GLuint, severity: GLenum, length: GLsizei, message: cstring, userParam: pointer) {.stdcall.} =
|
||||
|
|
|
@ -46,6 +46,8 @@ import ../screen
|
|||
import ../util
|
||||
import ../graphics/render
|
||||
import ../input
|
||||
from loadable import terminateLoadableWorkerThreads
|
||||
from ../gpu_formats/texture_optimize import terminateTextureWorkerThreads
|
||||
|
||||
proc screen*(window: Window): Screen {.inline.} =
|
||||
cast[Screen](window.getWindowUserPointer)
|
||||
|
@ -232,6 +234,8 @@ proc start_platform_main_loop*(engine: MyouEngine, main_loop: proc(self: MyouEng
|
|||
for i in 0 ..< window.screen.frame_interval:
|
||||
window.swapBuffers()
|
||||
glfw.pollEvents()
|
||||
terminateLoadableWorkerThreads()
|
||||
terminateTextureWorkerThreads()
|
||||
glfw.terminate()
|
||||
|
||||
proc myouAndroidGetActivity*(): pointer =
|
||||
|
|
|
@ -652,6 +652,8 @@ type
|
|||
format*: TextureFormat
|
||||
filter*: TextureFilter
|
||||
is_framebuffer_active*: bool ## private
|
||||
is_sRGB*: bool
|
||||
is_compressed*: bool
|
||||
mipmap_range*: (int, int)
|
||||
|
||||
# framebuffer.nim
|
||||
|
|
Loading…
Reference in a new issue