2019-02-20 15:23:23 +01:00
#include "libslic3r/libslic3r.h"
2018-06-13 09:12:16 +02:00
#include "GLTexture.hpp"
2019-03-27 14:42:09 +01:00
#include "3DScene.hpp"
2018-06-13 09:12:16 +02:00
#include <GL/glew.h>
#include <wx/image.h>
2018-06-21 09:54:43 +02:00
#include <boost/filesystem.hpp>
2019-02-20 15:23:23 +01:00
#include <boost/algorithm/string/predicate.hpp>
2018-06-21 09:54:43 +02:00
2018-06-13 09:12:16 +02:00
#include <vector>
2018-06-14 10:37:28 +02:00
#include <algorithm>
2019-05-31 15:25:02 +02:00
#include <thread>
#define STB_DXT_IMPLEMENTATION
#include "stb_dxt/stb_dxt.h"
2018-06-13 09:12:16 +02:00
2019-02-20 15:23:23 +01:00
#include "nanosvg/nanosvg.h"
#include "nanosvg/nanosvgrast.h"
2019-02-27 10:28:36 +01:00
#include "libslic3r/Utils.hpp"
2019-02-27 10:03:58 +01:00
2018-06-13 09:12:16 +02:00
namespace Slic3r {
namespace GUI {
2019-05-31 15:25:02 +02:00
void GLTexture :: Compressor :: reset ()
{
2019-08-06 11:29:26 +02:00
if ( m_thread . joinable ()) {
2019-08-01 11:01:18 +02:00
m_abort_compressing = true ;
2019-08-06 11:29:26 +02:00
m_thread . join ();
m_levels . clear ();
m_num_levels_compressed = 0 ;
m_abort_compressing = false ;
}
assert ( m_levels . empty ());
assert ( m_abort_compressing == false );
assert ( m_num_levels_compressed == 0 );
2019-05-31 15:25:02 +02:00
}
void GLTexture :: Compressor :: start_compressing ()
{
2019-08-06 11:29:26 +02:00
// The worker thread should be stopped already.
assert ( ! m_thread . joinable ());
assert ( ! m_levels . empty ());
assert ( m_abort_compressing == false );
assert ( m_num_levels_compressed == 0 );
if ( ! m_levels . empty ()) {
std :: thread thrd ( & GLTexture :: Compressor :: compress , this );
m_thread = std :: move ( thrd );
}
2019-05-31 15:25:02 +02:00
}
bool GLTexture :: Compressor :: unsent_compressed_data_available () const
{
2019-08-06 11:29:26 +02:00
if ( m_levels . empty ())
return false ;
// Querying the atomic m_num_levels_compressed value synchronizes processor caches, so that the dat of m_levels modified by the worker thread are accessible to the calling thread.
unsigned int num_compressed = m_num_levels_compressed ;
for ( unsigned int i = 0 ; i < num_compressed ; ++ i )
if ( ! m_levels [ i ]. sent_to_gpu && ! m_levels [ i ]. compressed_data . empty ())
2019-05-31 15:25:02 +02:00
return true ;
return false ;
}
void GLTexture :: Compressor :: send_compressed_data_to_gpu ()
{
2019-06-03 13:53:30 +02:00
// this method should be called inside the main thread of Slicer or a new OpenGL context (sharing resources) would be needed
2019-08-06 11:29:26 +02:00
if ( m_levels . empty ())
return ;
2019-06-03 13:53:30 +02:00
2019-05-31 15:25:02 +02:00
glsafe ( :: glPixelStorei ( GL_UNPACK_ALIGNMENT , 1 ));
glsafe ( :: glBindTexture ( GL_TEXTURE_2D , m_texture . m_id ));
2019-08-06 11:29:26 +02:00
// Querying the atomic m_num_levels_compressed value synchronizes processor caches, so that the dat of m_levels modified by the worker thread are accessible to the calling thread.
int num_compressed = ( int ) m_num_levels_compressed ;
for ( int i = 0 ; i < num_compressed ; ++ i )
2019-05-31 15:25:02 +02:00
{
Level & level = m_levels [ i ];
2019-08-06 11:29:26 +02:00
if ( ! level . sent_to_gpu && ! level . compressed_data . empty ())
2019-05-31 15:25:02 +02:00
{
glsafe ( :: glCompressedTexSubImage2D ( GL_TEXTURE_2D , ( GLint ) i , 0 , 0 , ( GLsizei ) level . w , ( GLsizei ) level . h , GL_COMPRESSED_RGBA_S3TC_DXT5_EXT , ( GLsizei ) level . compressed_data . size (), ( const GLvoid * ) level . compressed_data . data ()));
glsafe ( :: glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MAX_LEVEL , i ));
glsafe ( :: glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MIN_FILTER , ( i > 0 ) ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR ));
level . sent_to_gpu = true ;
// we are done with the compressed data, we can discard it
level . compressed_data . clear ();
}
}
glsafe ( :: glBindTexture ( GL_TEXTURE_2D , 0 ));
2019-08-06 11:29:26 +02:00
if ( num_compressed == ( unsigned int ) m_levels . size ())
// Finalize the worker thread, close it.
this -> reset ();
2019-06-05 10:07:59 +02:00
}
2019-05-31 15:25:02 +02:00
void GLTexture :: Compressor :: compress ()
{
// reference: https://github.com/Cyan4973/RygsDXTc
2019-08-06 11:29:26 +02:00
assert ( m_num_levels_compressed == 0 );
assert ( m_abort_compressing == false );
2019-08-01 11:01:18 +02:00
2019-05-31 15:25:02 +02:00
for ( Level & level : m_levels )
{
if ( m_abort_compressing )
break ;
// stb_dxt library, despite claiming that the needed size of the destination buffer is equal to (source buffer size)/4,
// crashes if doing so, so we start with twice the required size
level . compressed_data = std :: vector < unsigned char > ( level . w * level . h * 2 , 0 );
int compressed_size = 0 ;
rygCompress ( level . compressed_data . data (), level . src_data . data (), level . w , level . h , 1 , compressed_size );
level . compressed_data . resize ( compressed_size );
// we are done with the source data, we can discard it
level . src_data . clear ();
2019-08-06 11:29:26 +02:00
++ m_num_levels_compressed ;
2019-05-31 15:25:02 +02:00
}
}
2018-07-31 12:25:00 +02:00
GLTexture :: Quad_UVs GLTexture :: FullTextureUVs = { { 0.0f , 1.0f }, { 1.0f , 1.0f }, { 1.0f , 0.0f }, { 0.0f , 0.0f } };
2018-06-13 09:12:16 +02:00
GLTexture :: GLTexture ()
: m_id ( 0 )
, m_width ( 0 )
, m_height ( 0 )
, m_source ( "" )
2019-05-31 15:25:02 +02:00
, m_compressor ( * this )
2018-06-13 09:12:16 +02:00
{
}
GLTexture ::~ GLTexture ()
{
reset ();
}
2019-07-19 11:18:19 +02:00
bool GLTexture :: load_from_file ( const std :: string & filename , bool use_mipmaps , ECompressionType compression_type , bool apply_anisotropy )
2019-02-20 15:23:23 +01:00
{
reset ();
if ( ! boost :: filesystem :: exists ( filename ))
return false ;
if ( boost :: algorithm :: iends_with ( filename , ".png" ))
2019-07-19 11:18:19 +02:00
return load_from_png ( filename , use_mipmaps , compression_type , apply_anisotropy );
2019-07-19 09:18:09 +02:00
else
return false ;
}
2019-05-28 15:21:34 +02:00
bool GLTexture :: load_from_svg_file ( const std :: string & filename , bool use_mipmaps , bool compress , bool apply_anisotropy , unsigned int max_size_px )
2019-02-20 15:23:23 +01:00
{
reset ();
if ( ! boost :: filesystem :: exists ( filename ))
return false ;
if ( boost :: algorithm :: iends_with ( filename , ".svg" ))
2019-05-28 15:21:34 +02:00
return load_from_svg ( filename , use_mipmaps , compress , apply_anisotropy , max_size_px );
2019-02-20 15:23:23 +01:00
else
return false ;
}
2018-06-13 09:12:16 +02:00
2019-05-28 12:53:16 +02:00
bool GLTexture :: load_from_svg_files_as_sprites_array ( const std :: vector < std :: string >& filenames , const std :: vector < std :: pair < int , bool >>& states , unsigned int sprite_size_px , bool compress )
2019-02-26 09:56:23 +01:00
{
reset ();
2019-02-26 12:56:13 +01:00
if ( filenames . empty () || states . empty () || ( sprite_size_px == 0 ))
2019-02-26 09:56:23 +01:00
return false ;
2019-02-26 12:56:13 +01:00
m_width = ( int )( sprite_size_px * states . size ());
2019-02-26 10:40:00 +01:00
m_height = ( int )( sprite_size_px * filenames . size ());
2019-02-26 09:56:23 +01:00
int n_pixels = m_width * m_height ;
2019-02-26 12:56:13 +01:00
int sprite_n_pixels = sprite_size_px * sprite_size_px ;
int sprite_bytes = sprite_n_pixels * 4 ;
int sprite_stride = sprite_size_px * 4 ;
2019-02-26 09:56:23 +01:00
if ( n_pixels <= 0 )
{
reset ();
return false ;
}
std :: vector < unsigned char > data ( n_pixels * 4 , 0 );
2019-02-26 12:56:13 +01:00
std :: vector < unsigned char > sprite_data ( sprite_bytes , 0 );
std :: vector < unsigned char > sprite_white_only_data ( sprite_bytes , 0 );
std :: vector < unsigned char > sprite_gray_only_data ( sprite_bytes , 0 );
std :: vector < unsigned char > output_data ( sprite_bytes , 0 );
2019-02-26 09:56:23 +01:00
NSVGrasterizer * rast = nsvgCreateRasterizer ();
if ( rast == nullptr )
{
reset ();
return false ;
}
int sprite_id = - 1 ;
for ( const std :: string & filename : filenames )
{
++ sprite_id ;
if ( ! boost :: filesystem :: exists ( filename ))
continue ;
if ( ! boost :: algorithm :: iends_with ( filename , ".svg" ))
continue ;
2019-03-27 15:42:01 +01:00
NSVGimage * image = nsvgParseFromFile ( filename . c_str (), "px" , 96.0f );
2019-02-26 09:56:23 +01:00
if ( image == nullptr )
continue ;
float scale = ( float ) sprite_size_px / std :: max ( image -> width , image -> height );
2019-02-26 12:56:13 +01:00
nsvgRasterize ( rast , image , 0 , 0 , scale , sprite_data . data (), sprite_size_px , sprite_size_px , sprite_stride );
// makes white only copy of the sprite
:: memcpy (( void * ) sprite_white_only_data . data (), ( const void * ) sprite_data . data (), sprite_bytes );
for ( int i = 0 ; i < sprite_n_pixels ; ++ i )
{
2019-02-27 10:03:58 +01:00
int offset = i * 4 ;
if ( sprite_white_only_data . data ()[ offset ] != 0 )
:: memset (( void * ) & sprite_white_only_data . data ()[ offset ], 255 , 3 );
2019-02-26 12:56:13 +01:00
}
// makes gray only copy of the sprite
:: memcpy (( void * ) sprite_gray_only_data . data (), ( const void * ) sprite_data . data (), sprite_bytes );
for ( int i = 0 ; i < sprite_n_pixels ; ++ i )
{
2019-02-27 10:03:58 +01:00
int offset = i * 4 ;
if ( sprite_gray_only_data . data ()[ offset ] != 0 )
:: memset (( void * ) & sprite_gray_only_data . data ()[ offset ], 128 , 3 );
2019-02-26 12:56:13 +01:00
}
2019-02-26 09:56:23 +01:00
int sprite_offset_px = sprite_id * sprite_size_px * m_width ;
2019-02-26 12:56:13 +01:00
int state_id = - 1 ;
for ( const std :: pair < int , bool >& state : states )
2019-02-26 09:56:23 +01:00
{
2019-02-26 12:56:13 +01:00
++ state_id ;
// select the sprite variant
std :: vector < unsigned char >* src = nullptr ;
switch ( state . first )
{
case 1 : { src = & sprite_white_only_data ; break ; }
case 2 : { src = & sprite_gray_only_data ; break ; }
default : { src = & sprite_data ; break ; }
}
:: memcpy (( void * ) output_data . data (), ( const void * ) src -> data (), sprite_bytes );
// applies background, if needed
if ( state . second )
{
for ( int i = 0 ; i < sprite_n_pixels ; ++ i )
{
2019-02-27 10:03:58 +01:00
int offset = i * 4 ;
float alpha = ( float ) output_data . data ()[ offset + 3 ] / 255.0f ;
output_data . data ()[ offset + 0 ] = ( unsigned char )( output_data . data ()[ offset + 0 ] * alpha );
output_data . data ()[ offset + 1 ] = ( unsigned char )( output_data . data ()[ offset + 1 ] * alpha );
output_data . data ()[ offset + 2 ] = ( unsigned char )( output_data . data ()[ offset + 2 ] * alpha );
output_data . data ()[ offset + 3 ] = ( unsigned char )( 128 * ( 1.0f - alpha ) + output_data . data ()[ offset + 3 ] * alpha );
2019-02-26 12:56:13 +01:00
}
}
int state_offset_px = sprite_offset_px + state_id * sprite_size_px ;
2019-03-05 10:54:03 +01:00
for ( int j = 0 ; j < ( int ) sprite_size_px ; ++ j )
2019-02-26 09:56:23 +01:00
{
2019-02-27 10:03:58 +01:00
:: memcpy (( void * ) & data . data ()[( state_offset_px + j * m_width ) * 4 ], ( const void * ) & output_data . data ()[ j * sprite_stride ], sprite_stride );
2019-02-26 09:56:23 +01:00
}
}
nsvgDelete ( image );
}
nsvgDeleteRasterizer ( rast );
// sends data to gpu
2019-03-27 14:42:09 +01:00
glsafe ( :: glPixelStorei ( GL_UNPACK_ALIGNMENT , 1 ));
glsafe ( :: glGenTextures ( 1 , & m_id ));
glsafe ( :: glBindTexture ( GL_TEXTURE_2D , m_id ));
2019-05-28 12:53:16 +02:00
if ( compress && GLEW_EXT_texture_compression_s3tc )
2019-05-21 14:19:03 +02:00
glsafe ( :: glTexImage2D ( GL_TEXTURE_2D , 0 , GL_COMPRESSED_RGBA_S3TC_DXT5_EXT , ( GLsizei ) m_width , ( GLsizei ) m_height , 0 , GL_RGBA , GL_UNSIGNED_BYTE , ( const void * ) data . data ()));
else
glsafe ( :: glTexImage2D ( GL_TEXTURE_2D , 0 , GL_RGBA , ( GLsizei ) m_width , ( GLsizei ) m_height , 0 , GL_RGBA , GL_UNSIGNED_BYTE , ( const void * ) data . data ()));
2019-03-27 14:42:09 +01:00
glsafe ( :: glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MIN_FILTER , GL_LINEAR ));
glsafe ( :: glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MAX_LEVEL , 0 ));
glsafe ( :: glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MAG_FILTER , GL_LINEAR ));
2019-02-26 09:56:23 +01:00
2019-03-27 14:42:09 +01:00
glsafe ( :: glBindTexture ( GL_TEXTURE_2D , 0 ));
2019-02-26 09:56:23 +01:00
m_source = filenames . front ();
2019-02-26 12:56:13 +01:00
#if 0
// debug output
static int pass = 0;
++pass;
2019-02-26 09:56:23 +01:00
wxImage output(m_width, m_height);
output.InitAlpha();
for (int h = 0; h < m_height; ++h)
{
2019-02-27 10:03:58 +01:00
int px_h = h * m_width;
2019-02-26 09:56:23 +01:00
for (int w = 0; w < m_width; ++w)
{
2019-02-27 10:03:58 +01:00
int offset = (px_h + w) * 4;
output.SetRGB(w, h, data.data()[offset + 0], data.data()[offset + 1], data.data()[offset + 2]);
output.SetAlpha(w, h, data.data()[offset + 3]);
2019-02-26 09:56:23 +01:00
}
}
2019-02-27 10:03:58 +01:00
std::string out_filename = resources_dir() + "/icons/test_" + std::to_string(pass) + ".png";
output.SaveFile(out_filename, wxBITMAP_TYPE_PNG);
2019-02-26 12:56:13 +01:00
#endif // 0
2019-02-26 09:56:23 +01:00
return true ;
}
2018-06-13 09:12:16 +02:00
void GLTexture :: reset ()
{
if ( m_id != 0 )
2019-03-27 14:42:09 +01:00
glsafe ( :: glDeleteTextures ( 1 , & m_id ));
2018-06-13 09:12:16 +02:00
m_id = 0 ;
m_width = 0 ;
m_height = 0 ;
m_source = "" ;
2019-05-31 15:25:02 +02:00
m_compressor . reset ();
2018-06-13 09:12:16 +02:00
}
void GLTexture :: render_texture ( unsigned int tex_id , float left , float right , float bottom , float top )
2018-07-31 12:25:00 +02:00
{
render_sub_texture ( tex_id , left , right , bottom , top , FullTextureUVs );
}
void GLTexture :: render_sub_texture ( unsigned int tex_id , float left , float right , float bottom , float top , const GLTexture :: Quad_UVs & uvs )
2018-06-13 09:12:16 +02:00
{
2019-03-27 14:42:09 +01:00
glsafe ( :: glEnable ( GL_BLEND ));
glsafe ( :: glBlendFunc ( GL_SRC_ALPHA , GL_ONE_MINUS_SRC_ALPHA ));
2018-06-22 15:11:04 +02:00
2019-03-27 14:42:09 +01:00
glsafe ( :: glEnable ( GL_TEXTURE_2D ));
glsafe ( :: glTexEnvi ( GL_TEXTURE_ENV , GL_TEXTURE_ENV_MODE , GL_REPLACE ));
2018-06-13 09:12:16 +02:00
2019-03-27 14:42:09 +01:00
glsafe ( :: glBindTexture ( GL_TEXTURE_2D , ( GLuint ) tex_id ));
2018-06-13 09:12:16 +02:00
:: glBegin ( GL_QUADS );
2018-07-31 12:25:00 +02:00
:: glTexCoord2f ( uvs . left_bottom . u , uvs . left_bottom . v ); :: glVertex2f ( left , bottom );
:: glTexCoord2f ( uvs . right_bottom . u , uvs . right_bottom . v ); :: glVertex2f ( right , bottom );
:: glTexCoord2f ( uvs . right_top . u , uvs . right_top . v ); :: glVertex2f ( right , top );
:: glTexCoord2f ( uvs . left_top . u , uvs . left_top . v ); :: glVertex2f ( left , top );
2019-03-27 14:42:09 +01:00
glsafe ( :: glEnd ());
2018-06-13 09:12:16 +02:00
2019-03-27 14:42:09 +01:00
glsafe ( :: glBindTexture ( GL_TEXTURE_2D , 0 ));
2018-06-13 09:12:16 +02:00
2019-03-27 14:42:09 +01:00
glsafe ( :: glDisable ( GL_TEXTURE_2D ));
glsafe ( :: glDisable ( GL_BLEND ));
2018-06-13 09:12:16 +02:00
}
2019-07-19 11:18:19 +02:00
bool GLTexture :: load_from_png ( const std :: string & filename , bool use_mipmaps , ECompressionType compression_type , bool apply_anisotropy )
2018-06-14 10:37:28 +02:00
{
2019-07-19 11:18:19 +02:00
bool compression_enabled = ( compression_type != None ) && GLEW_EXT_texture_compression_s3tc ;
2019-07-19 09:18:09 +02:00
// Load a PNG with an alpha channel.
wxImage image ;
if ( ! image . LoadFile ( wxString :: FromUTF8 ( filename . c_str ()), wxBITMAP_TYPE_PNG ))
{
reset ();
return false ;
}
m_width = image . GetWidth ();
m_height = image . GetHeight ();
bool requires_rescale = false ;
2019-07-19 11:18:19 +02:00
if ( compression_enabled && ( compression_type == MultiThreaded ))
2019-07-19 09:18:09 +02:00
{
// the stb_dxt compression library seems to like only texture sizes which are a multiple of 4
int width_rem = m_width % 4 ;
int height_rem = m_height % 4 ;
if ( width_rem != 0 )
{
m_width += ( 4 - width_rem );
requires_rescale = true ;
}
if ( height_rem != 0 )
{
m_height += ( 4 - height_rem );
requires_rescale = true ;
}
}
if ( requires_rescale )
image = image . ResampleBicubic ( m_width , m_height );
int n_pixels = m_width * m_height ;
if ( n_pixels <= 0 )
{
reset ();
return false ;
}
// Get RGB & alpha raw data from wxImage, pack them into an array.
unsigned char * img_rgb = image . GetData ();
if ( img_rgb == nullptr )
{
reset ();
return false ;
}
unsigned char * img_alpha = image . GetAlpha ();
std :: vector < unsigned char > data ( n_pixels * 4 , 0 );
for ( int i = 0 ; i < n_pixels ; ++ i )
{
int data_id = i * 4 ;
int img_id = i * 3 ;
data [ data_id + 0 ] = img_rgb [ img_id + 0 ];
data [ data_id + 1 ] = img_rgb [ img_id + 1 ];
data [ data_id + 2 ] = img_rgb [ img_id + 2 ];
data [ data_id + 3 ] = ( img_alpha != nullptr ) ? img_alpha [ i ] : 255 ;
}
// sends data to gpu
glsafe ( :: glPixelStorei ( GL_UNPACK_ALIGNMENT , 1 ));
glsafe ( :: glGenTextures ( 1 , & m_id ));
glsafe ( :: glBindTexture ( GL_TEXTURE_2D , m_id ));
if ( apply_anisotropy )
{
GLfloat max_anisotropy = GLCanvas3DManager :: get_gl_info (). get_max_anisotropy ();
if ( max_anisotropy > 1.0f )
glsafe ( :: glTexParameterf ( GL_TEXTURE_2D , GL_TEXTURE_MAX_ANISOTROPY_EXT , max_anisotropy ));
}
if ( compression_enabled )
{
2019-07-19 11:18:19 +02:00
if ( compression_type == SingleThreaded )
glsafe ( :: glTexImage2D ( GL_TEXTURE_2D , 0 , GL_COMPRESSED_RGBA_S3TC_DXT5_EXT , ( GLsizei ) m_width , ( GLsizei ) m_height , 0 , GL_RGBA , GL_UNSIGNED_BYTE , ( const void * ) data . data ()));
else
{
// initializes the texture on GPU
glsafe ( :: glTexImage2D ( GL_TEXTURE_2D , 0 , GL_COMPRESSED_RGBA_S3TC_DXT5_EXT , ( GLsizei ) m_width , ( GLsizei ) m_height , 0 , GL_RGBA , GL_UNSIGNED_BYTE , 0 ));
// and send the uncompressed data to the compressor
m_compressor . add_level (( unsigned int ) m_width , ( unsigned int ) m_height , data );
}
2019-07-19 09:18:09 +02:00
}
else
glsafe ( :: glTexImage2D ( GL_TEXTURE_2D , 0 , GL_RGBA , ( GLsizei ) m_width , ( GLsizei ) m_height , 0 , GL_RGBA , GL_UNSIGNED_BYTE , ( const void * ) data . data ()));
if ( use_mipmaps )
{
// we manually generate mipmaps because glGenerateMipmap() function is not reliable on all graphics cards
int lod_w = m_width ;
int lod_h = m_height ;
GLint level = 0 ;
// we do not need to generate all levels down to 1x1
while (( lod_w > 16 ) || ( lod_h > 16 ))
{
++ level ;
lod_w = std :: max ( lod_w / 2 , 1 );
lod_h = std :: max ( lod_h / 2 , 1 );
n_pixels = lod_w * lod_h ;
image = image . ResampleBicubic ( lod_w , lod_h );
data . resize ( n_pixels * 4 );
img_rgb = image . GetData ();
img_alpha = image . GetAlpha ();
for ( int i = 0 ; i < n_pixels ; ++ i )
{
int data_id = i * 4 ;
int img_id = i * 3 ;
data [ data_id + 0 ] = img_rgb [ img_id + 0 ];
data [ data_id + 1 ] = img_rgb [ img_id + 1 ];
data [ data_id + 2 ] = img_rgb [ img_id + 2 ];
data [ data_id + 3 ] = ( img_alpha != nullptr ) ? img_alpha [ i ] : 255 ;
}
if ( compression_enabled )
{
2019-07-19 11:18:19 +02:00
if ( compression_type == SingleThreaded )
glsafe ( :: glTexImage2D ( GL_TEXTURE_2D , level , GL_COMPRESSED_RGBA_S3TC_DXT5_EXT , ( GLsizei ) m_width , ( GLsizei ) m_height , 0 , GL_RGBA , GL_UNSIGNED_BYTE , ( const void * ) data . data ()));
else
{
// initializes the texture on GPU
glsafe ( :: glTexImage2D ( GL_TEXTURE_2D , level , GL_COMPRESSED_RGBA_S3TC_DXT5_EXT , ( GLsizei ) lod_w , ( GLsizei ) lod_h , 0 , GL_RGBA , GL_UNSIGNED_BYTE , 0 ));
// and send the uncompressed data to the compressor
m_compressor . add_level (( unsigned int ) lod_w , ( unsigned int ) lod_h , data );
}
2019-07-19 09:18:09 +02:00
}
else
glsafe ( :: glTexImage2D ( GL_TEXTURE_2D , level , GL_RGBA , ( GLsizei ) lod_w , ( GLsizei ) lod_h , 0 , GL_RGBA , GL_UNSIGNED_BYTE , ( const void * ) data . data ()));
}
if ( ! compression_enabled )
{
glsafe ( :: glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MAX_LEVEL , level ));
glsafe ( :: glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MIN_FILTER , GL_LINEAR_MIPMAP_LINEAR ));
}
}
else
{
glsafe ( :: glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MIN_FILTER , GL_LINEAR ));
glsafe ( :: glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MAX_LEVEL , 0 ));
}
glsafe ( :: glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MAG_FILTER , GL_LINEAR ));
glsafe ( :: glBindTexture ( GL_TEXTURE_2D , 0 ));
m_source = filename ;
2019-07-19 11:18:19 +02:00
if ( compression_enabled && ( compression_type == MultiThreaded ))
2019-07-19 09:18:09 +02:00
// start asynchronous compression
m_compressor . start_compressing ();
return true ;
}
2019-05-28 15:21:34 +02:00
bool GLTexture :: load_from_svg ( const std :: string & filename , bool use_mipmaps , bool compress , bool apply_anisotropy , unsigned int max_size_px )
2019-02-20 15:23:23 +01:00
{
2019-05-31 15:25:02 +02:00
bool compression_enabled = compress && GLEW_EXT_texture_compression_s3tc ;
2019-03-27 15:42:01 +01:00
NSVGimage * image = nsvgParseFromFile ( filename . c_str (), "px" , 96.0f );
2019-02-20 15:23:23 +01:00
if ( image == nullptr )
{
reset ();
return false ;
}
float scale = ( float ) max_size_px / std :: max ( image -> width , image -> height );
m_width = ( int )( scale * image -> width );
m_height = ( int )( scale * image -> height );
2019-05-31 15:25:02 +02:00
if ( compression_enabled )
{
// the stb_dxt compression library seems to like only texture sizes which are a multiple of 4
int width_rem = m_width % 4 ;
int height_rem = m_height % 4 ;
if ( width_rem != 0 )
m_width += ( 4 - width_rem );
if ( height_rem != 0 )
m_height += ( 4 - height_rem );
}
2019-02-20 15:23:23 +01:00
int n_pixels = m_width * m_height ;
if ( n_pixels <= 0 )
{
reset ();
2019-04-04 09:20:11 +02:00
nsvgDelete ( image );
2019-02-20 15:23:23 +01:00
return false ;
}
NSVGrasterizer * rast = nsvgCreateRasterizer ();
if ( rast == nullptr )
{
nsvgDelete ( image );
reset ();
return false ;
}
// creates the temporary buffer only once, with max size, and reuse it for all the levels, if generating mipmaps
std :: vector < unsigned char > data ( n_pixels * 4 , 0 );
nsvgRasterize ( rast , image , 0 , 0 , scale , data . data (), m_width , m_height , m_width * 4 );
// sends data to gpu
2019-03-27 14:42:09 +01:00
glsafe ( :: glPixelStorei ( GL_UNPACK_ALIGNMENT , 1 ));
glsafe ( :: glGenTextures ( 1 , & m_id ));
glsafe ( :: glBindTexture ( GL_TEXTURE_2D , m_id ));
2019-06-24 09:54:58 +02:00
2019-05-28 15:21:34 +02:00
if ( apply_anisotropy )
{
GLfloat max_anisotropy = GLCanvas3DManager :: get_gl_info (). get_max_anisotropy ();
if ( max_anisotropy > 1.0f )
glsafe ( :: glTexParameterf ( GL_TEXTURE_2D , GL_TEXTURE_MAX_ANISOTROPY_EXT , max_anisotropy ));
}
2019-05-31 15:25:02 +02:00
if ( compression_enabled )
{
// initializes the texture on GPU
glsafe ( :: glTexImage2D ( GL_TEXTURE_2D , 0 , GL_COMPRESSED_RGBA_S3TC_DXT5_EXT , ( GLsizei ) m_width , ( GLsizei ) m_height , 0 , GL_RGBA , GL_UNSIGNED_BYTE , 0 ));
// and send the uncompressed data to the compressor
m_compressor . add_level (( unsigned int ) m_width , ( unsigned int ) m_height , data );
}
2019-05-21 14:19:03 +02:00
else
glsafe ( :: glTexImage2D ( GL_TEXTURE_2D , 0 , GL_RGBA , ( GLsizei ) m_width , ( GLsizei ) m_height , 0 , GL_RGBA , GL_UNSIGNED_BYTE , ( const void * ) data . data ()));
2019-06-24 09:54:58 +02:00
2019-02-20 15:23:23 +01:00
if ( use_mipmaps )
{
// we manually generate mipmaps because glGenerateMipmap() function is not reliable on all graphics cards
int lod_w = m_width ;
int lod_h = m_height ;
GLint level = 0 ;
2019-05-31 15:25:02 +02:00
// we do not need to generate all levels down to 1x1
2019-06-02 11:01:51 +02:00
while (( lod_w > 16 ) || ( lod_h > 16 ))
2019-02-20 15:23:23 +01:00
{
++ level ;
lod_w = std :: max ( lod_w / 2 , 1 );
lod_h = std :: max ( lod_h / 2 , 1 );
scale /= 2.0f ;
2019-05-31 15:25:02 +02:00
data . resize ( lod_w * lod_h * 4 );
2019-02-20 15:23:23 +01:00
nsvgRasterize ( rast , image , 0 , 0 , scale , data . data (), lod_w , lod_h , lod_w * 4 );
2019-05-31 15:25:02 +02:00
if ( compression_enabled )
{
// initializes the texture on GPU
glsafe ( :: glTexImage2D ( GL_TEXTURE_2D , level , GL_COMPRESSED_RGBA_S3TC_DXT5_EXT , ( GLsizei ) lod_w , ( GLsizei ) lod_h , 0 , GL_RGBA , GL_UNSIGNED_BYTE , 0 ));
// and send the uncompressed data to the compressor
m_compressor . add_level (( unsigned int ) lod_w , ( unsigned int ) lod_h , data );
}
2019-05-21 14:19:03 +02:00
else
glsafe ( :: glTexImage2D ( GL_TEXTURE_2D , level , GL_RGBA , ( GLsizei ) lod_w , ( GLsizei ) lod_h , 0 , GL_RGBA , GL_UNSIGNED_BYTE , ( const void * ) data . data ()));
2019-02-20 15:23:23 +01:00
}
2019-05-31 15:25:02 +02:00
if ( ! compression_enabled )
{
glsafe ( :: glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MAX_LEVEL , level ));
glsafe ( :: glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MIN_FILTER , GL_LINEAR_MIPMAP_LINEAR ));
}
2019-02-20 15:23:23 +01:00
}
else
{
2019-03-27 14:42:09 +01:00
glsafe ( :: glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MIN_FILTER , GL_LINEAR ));
glsafe ( :: glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MAX_LEVEL , 0 ));
2019-02-20 15:23:23 +01:00
}
2019-07-19 09:18:09 +02:00
2019-03-27 14:42:09 +01:00
glsafe ( :: glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MAG_FILTER , GL_LINEAR ));
2019-02-20 15:23:23 +01:00
2019-03-27 14:42:09 +01:00
glsafe ( :: glBindTexture ( GL_TEXTURE_2D , 0 ));
2019-02-20 15:23:23 +01:00
m_source = filename ;
2019-05-31 15:25:02 +02:00
if ( compression_enabled )
// start asynchronous compression
m_compressor . start_compressing ();
2019-02-20 15:23:23 +01:00
nsvgDeleteRasterizer ( rast );
nsvgDelete ( image );
return true ;
}
2018-06-13 09:12:16 +02:00
} // namespace GUI
} // namespace Slic3r