From d3e48d8c31ddd5e1996570ba53f0321d65155d17 Mon Sep 17 00:00:00 2001 From: Garux Date: Mon, 18 Jul 2022 10:05:19 +0300 Subject: [PATCH] * render Q3 shader based skyboxes logic: load 6 skybox textures when shader gets used by scene, don't unload dynamically, just on 'flush' texture browser only uses normal preview image and doesn't trigger potentially heavy box loading also fix R_ResampleTexture for [2+x upscaling --- include/iglrender.h | 1 + include/ishaders.h | 1 + include/itextures.h | 3 +- plugins/shaders/shaders.cpp | 26 +++++++ radiant/camwindow.cpp | 3 +- radiant/renderstate.cpp | 111 +++++++++++++++++++++++++++-- radiant/texmanip.cpp | 2 + radiant/textures.cpp | 73 +++++++++++++++---- radiant/xywindow.cpp | 4 +- setup/data/tools/gl/skybox_fp.glsl | 8 +++ setup/data/tools/gl/skybox_vp.glsl | 8 +++ 11 files changed, 219 insertions(+), 21 deletions(-) create mode 100644 setup/data/tools/gl/skybox_fp.glsl create mode 100644 setup/data/tools/gl/skybox_vp.glsl diff --git a/include/iglrender.h b/include/iglrender.h index f36a261a9..6e6f015e3 100644 --- a/include/iglrender.h +++ b/include/iglrender.h @@ -82,6 +82,7 @@ class OpenGLState GLint m_texture5; GLint m_texture6; GLint m_texture7; + GLint m_textureSkyBox; Vector4 m_colour; GLenum m_blend_src, m_blend_dst; GLenum m_depthfunc; diff --git a/include/ishaders.h b/include/ishaders.h index e48ba4cfd..1a0cd2527 100644 --- a/include/ishaders.h +++ b/include/ishaders.h @@ -98,6 +98,7 @@ class IShader virtual void DecRef() = 0; // get/set the qtexture_t* Radiant uses to represent this shader object virtual qtexture_t* getTexture() const = 0; + virtual qtexture_t* getSkyBox() = 0; virtual qtexture_t* getDiffuse() const = 0; virtual qtexture_t* getBump() const = 0; virtual qtexture_t* getSpecular() const = 0; diff --git a/include/itextures.h b/include/itextures.h index f57dc2ece..470f297fb 100644 --- a/include/itextures.h +++ b/include/itextures.h @@ -32,8 +32,9 @@ class LoadImageCallback public: void* m_environment; LoadFunc m_func; + bool m_skybox; - LoadImageCallback( void* environment, LoadFunc func ) : m_environment( environment ), m_func( func ){ + LoadImageCallback( void* environment, LoadFunc func, bool skybox = false ) : m_environment( environment ), m_func( func ), m_skybox( skybox ){ } Image* loadImage( const char* name ) const { return m_func( m_environment, name ); diff --git a/plugins/shaders/shaders.cpp b/plugins/shaders/shaders.cpp index 1f94adb4a..c1f00bc19 100644 --- a/plugins/shaders/shaders.cpp +++ b/plugins/shaders/shaders.cpp @@ -279,6 +279,7 @@ class ShaderTemplate ShaderParameters m_params; TextureExpression m_textureName; + TextureExpression m_skyBox; TextureExpression m_diffuse; TextureExpression m_bump; ShaderValue m_heightmapScale; @@ -838,6 +839,7 @@ class CShader : public IShader CopiedString m_Name; qtexture_t* m_pTexture; + qtexture_t* m_pSkyBox; qtexture_t* m_notfound; qtexture_t* m_pDiffuse; float m_heightmapScale; @@ -860,6 +862,7 @@ class CShader : public IShader m_blendFunc( BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA ), m_bInUse( false ){ m_pTexture = 0; + m_pSkyBox = 0; m_pDiffuse = 0; m_pBump = 0; m_pSpecular = 0; @@ -893,6 +896,13 @@ class CShader : public IShader qtexture_t* getTexture() const { return m_pTexture; } + qtexture_t* getSkyBox() override { + /* load skybox if only used */ + if( m_pSkyBox == nullptr && !m_template.m_skyBox.empty() ) + m_pSkyBox = GlobalTexturesCache().capture( LoadImageCallback( 0, GlobalTexturesCache().defaultLoader().m_func, true ), m_template.m_skyBox.c_str() ); + + return m_pSkyBox; + } qtexture_t* getDiffuse() const { return m_pDiffuse; } @@ -966,6 +976,10 @@ class CShader : public IShader GlobalTexturesCache().release( m_notfound ); } + if ( m_pSkyBox != 0 ) { + GlobalTexturesCache().release( m_pSkyBox ); + } + unrealiseLighting(); } @@ -1220,6 +1234,18 @@ bool ShaderTemplate::parseQuake3( Tokeniser& tokeniser ){ RETURN_FALSE_IF_FAIL( Tokeniser_getFloat( tokeniser, m_AlphaRef ) ); } + else if ( string_equal_nocase( token, "skyparms" ) ) { + const char* sky = tokeniser.getToken(); + + if ( sky == 0 ) { + Tokeniser_unexpectedError( tokeniser, sky, "#skyparms" ); + return false; + } + + if( !string_equal( sky, "-" ) ){ + m_skyBox = sky; + } + } else if ( string_equal_nocase( token, "cull" ) ) { const char* cull = tokeniser.getToken(); diff --git a/radiant/camwindow.cpp b/radiant/camwindow.cpp index e01ce51b4..74e897174 100644 --- a/radiant/camwindow.cpp +++ b/radiant/camwindow.cpp @@ -2091,7 +2091,8 @@ void CamWnd::Cam_Draw(){ | RENDER_LIGHTING | RENDER_TEXTURE | RENDER_SMOOTH - | RENDER_SCALED; + | RENDER_SCALED + | RENDER_PROGRAM; break; case cd_lighting: globalstate |= RENDER_FILL diff --git a/radiant/renderstate.cpp b/radiant/renderstate.cpp index bde7b2a5c..4ce1e75ee 100644 --- a/radiant/renderstate.cpp +++ b/radiant/renderstate.cpp @@ -365,6 +365,69 @@ class GLSLDepthFillProgram : public GLProgram GLSLDepthFillProgram g_depthFillGLSL; +class GLSLSkyboxProgram : public GLProgram +{ +public: + GLhandleARB m_program; + GLint u_view_origin; + + GLSLSkyboxProgram() : m_program( 0 ){ + } + + void create(){ + // create program + m_program = glCreateProgramObjectARB(); + + // create shader + { + StringOutputStream filename( 256 ); + createShader( m_program, filename( GlobalRadiant().getAppPath(), "gl/skybox_vp.glsl" ), GL_VERTEX_SHADER_ARB ); + createShader( m_program, filename( GlobalRadiant().getAppPath(), "gl/skybox_fp.glsl" ), GL_FRAGMENT_SHADER_ARB ); + } + + GLSLProgram_link( m_program ); + GLSLProgram_validate( m_program ); + + glUseProgramObjectARB( m_program ); + + u_view_origin = glGetUniformLocationARB( m_program, "u_view_origin" ); + + glUseProgramObjectARB( 0 ); + + GlobalOpenGL_debugAssertNoErrors(); + } + + void destroy(){ + glDeleteObjectARB( m_program ); + m_program = 0; + } + + void enable(){ + glUseProgramObjectARB( m_program ); + + GlobalOpenGL_debugAssertNoErrors(); + + debug_string( "enable skybox" ); + } + + void disable(){ + glUseProgramObjectARB( 0 ); + + GlobalOpenGL_debugAssertNoErrors(); + + debug_string( "disable skybox" ); + } + + void setParameters( const Vector3& viewer, const Matrix4& localToWorld, const Vector3& origin, const Vector3& colour, const Matrix4& world2light ){ + glUniform3fARB( u_view_origin, viewer.x(), viewer.y(), viewer.z() ); + + GlobalOpenGL_debugAssertNoErrors(); + } +}; + +GLSLSkyboxProgram g_skyboxGLSL; + + // ARB path void createProgram( const char* filename, GLenum type ){ @@ -790,6 +853,9 @@ inline bool OpenGLState_less( const OpenGLState& self, const OpenGLState& other if ( self.m_texture7 != other.m_texture7 ) { return self.m_texture7 < other.m_texture7; } + if ( self.m_textureSkyBox != other.m_textureSkyBox ) { + return self.m_textureSkyBox < other.m_textureSkyBox; + } //! Sort by state bit-vector. if ( self.m_state != other.m_state ) { return self.m_state < other.m_state; @@ -809,6 +875,7 @@ void OpenGLState_constructDefault( OpenGLState& state ){ state.m_texture5 = 0; state.m_texture6 = 0; state.m_texture7 = 0; + state.m_textureSkyBox = 0; state.m_colour[0] = 1; state.m_colour[1] = 1; @@ -1158,7 +1225,7 @@ class OpenGLShaderCache final : public ShaderCache, public TexturesCacheObserver bool m_lightingEnabled; bool m_lightingSupported; - bool m_useShaderLanguage; + const bool m_useShaderLanguage; public: OpenGLShaderCache() : @@ -1302,8 +1369,9 @@ class OpenGLShaderCache final : public ShaderCache, public TexturesCacheObserver } debug_string( "end rendering" ); - OpenGLState reset = current; /* popmatrix after RENDER_TEXT */ - reset.m_state = current.m_state & ~RENDER_TEXT; + OpenGLState reset = current; /* reset some states */ + reset.m_state = current.m_state & ~RENDER_TEXT; /* popmatrix after RENDER_TEXT */ + reset.m_program = nullptr; /* disable shader */ OpenGLState_apply( reset, current, globalstate ); } void realise(){ @@ -1320,6 +1388,9 @@ class OpenGLShaderCache final : public ShaderCache, public TexturesCacheObserver } } + if( lightingSupported() ) + g_skyboxGLSL.create(); + for ( Shaders::iterator i = m_shaders.begin(); i != m_shaders.end(); ++i ) { if ( !( *i ).value.empty() ) { @@ -1347,6 +1418,8 @@ class OpenGLShaderCache final : public ShaderCache, public TexturesCacheObserver g_depthFillARB.destroy(); } } + if( GlobalOpenGL().contextValid && lightingSupported() ) + g_skyboxGLSL.destroy(); } } bool realised(){ @@ -1645,7 +1718,7 @@ void OpenGLState_apply( const OpenGLState& self, OpenGLState& current, unsigned if ( program != current.m_program ) { if ( current.m_program != 0 ) { current.m_program->disable(); - glColor4fv( vector4_to_array( current.m_colour ) ); +//why? glColor4fv( vector4_to_array( current.m_colour ) ); debug_colour( "cleaning program" ); } @@ -1888,6 +1961,16 @@ void OpenGLState_apply( const OpenGLState& self, OpenGLState& current, unsigned } + if( current.m_textureSkyBox != self.m_textureSkyBox ){ + if ( GlobalOpenGL().GL_1_3() ) { + glActiveTexture( GL_TEXTURE0 ); + glClientActiveTexture( GL_TEXTURE0 ); + } + glBindTexture( GL_TEXTURE_CUBE_MAP, self.m_textureSkyBox ); + GlobalOpenGL_debugAssertNoErrors(); + current.m_textureSkyBox = self.m_textureSkyBox; + } + if ( state & RENDER_TEXTURE && self.m_colour[3] != current.m_colour[3] ) { debug_colour( "setting alpha" ); glColor4f( 1,1,1,self.m_colour[3] ); @@ -1932,6 +2015,11 @@ void OpenGLState_apply( const OpenGLState& self, OpenGLState& current, unsigned void Renderables_flush( OpenGLStateBucket::Renderables& renderables, OpenGLState& current, unsigned int globalstate, const Vector3& viewer ){ const Matrix4* transform = 0; glPushMatrix(); + + if ( current.m_program != 0 && current.m_textureSkyBox != 0 && globalstate & RENDER_PROGRAM ) { + current.m_program->setParameters( viewer, g_matrix4_identity, g_vector3_identity, g_vector3_identity, g_matrix4_identity ); + } + for ( OpenGLStateBucket::Renderables::const_iterator i = renderables.begin(); i != renderables.end(); ++i ) { //qglLoadMatrixf(i->m_transform); @@ -2421,6 +2509,19 @@ void OpenGLShader::construct( const char* name ){ bumpPass.m_blend_src = GL_ONE; bumpPass.m_blend_dst = GL_ONE; } + // g_ShaderCache->lightingSupported() as in GLSL is available + else if( m_shader->getSkyBox() != nullptr && m_shader->getSkyBox()->texture_number != 0 && g_ShaderCache->lightingSupported() ) + { + state.m_texture = m_shader->getTexture()->texture_number; + state.m_textureSkyBox = m_shader->getSkyBox()->texture_number; + + state.m_state = RENDER_FILL | RENDER_CULLFACE | RENDER_TEXTURE | RENDER_DEPTHTEST | RENDER_DEPTHWRITE | RENDER_COLOURWRITE | RENDER_PROGRAM; + state.m_colour.vec3() = m_shader->getTexture()->color; + state.m_colour[3] = 1.0f; + state.m_sort = OpenGLState::eSortFullbright; + + state.m_program = &g_skyboxGLSL; + } else { state.m_texture = m_shader->getTexture()->texture_number; @@ -2461,7 +2562,7 @@ void OpenGLShader::construct( const char* name ){ break; } } - reinterpret_cast( state.m_colour ) = m_shader->getTexture()->color; + state.m_colour.vec3() = m_shader->getTexture()->color; state.m_colour[3] = 1.0f; if ( ( m_shader->getFlags() & QER_TRANS ) != 0 ) { diff --git a/radiant/texmanip.cpp b/radiant/texmanip.cpp index fbeafc524..a01d01f48 100644 --- a/radiant/texmanip.cpp +++ b/radiant/texmanip.cpp @@ -209,6 +209,7 @@ void R_ResampleTexture( const void *indata, int inwidth, int inheight, void *out oldy = yi; } memcpy( out, row1, outwidth4 ); + out += outwidth4; } } } @@ -297,6 +298,7 @@ void R_ResampleTexture( const void *indata, int inwidth, int inheight, void *out oldy = yi; } memcpy( out, row1, outwidth3 ); + out += outwidth3; } } } diff --git a/radiant/textures.cpp b/radiant/textures.cpp index bf9e69148..51b50c632 100644 --- a/radiant/textures.cpp +++ b/radiant/textures.cpp @@ -33,6 +33,7 @@ #include "container/hashfunc.h" #include "container/cache.h" #include "generic/callback.h" +#include "stream/stringstream.h" #include "stringio.h" #include "image.h" @@ -334,19 +335,67 @@ typedef std::pair TextureKey; void qtexture_realise( qtexture_t& texture, const TextureKey& key ){ texture.texture_number = 0; if ( !string_empty( key.second.c_str() ) ) { - Image* image = key.first.loadImage( key.second.c_str() ); - if ( image != 0 ) { - LoadTextureRGBA( &texture, image->getRGBAPixels(), image->getWidth(), image->getHeight() ); - texture.surfaceFlags = image->getSurfaceFlags(); - texture.contentFlags = image->getContentFlags(); - texture.value = image->getValue(); - image->release(); - globalOutputStream() << "Loaded Texture: \"" << key.second << "\"\n"; - GlobalOpenGL_debugAssertNoErrors(); + if( !key.first.m_skybox ){ + Image* image = key.first.loadImage( key.second.c_str() ); + if ( image != 0 ) { + LoadTextureRGBA( &texture, image->getRGBAPixels(), image->getWidth(), image->getHeight() ); + texture.surfaceFlags = image->getSurfaceFlags(); + texture.contentFlags = image->getContentFlags(); + texture.value = image->getValue(); + image->release(); + globalOutputStream() << "Loaded Texture: \"" << key.second << "\"\n"; + GlobalOpenGL_debugAssertNoErrors(); + } + else + { + globalErrorStream() << "Texture load failed: \"" << key.second << "\"\n"; + } } - else - { - globalErrorStream() << "Texture load failed: \"" << key.second << "\"\n"; + else { + Image *images[6]{}; + /* load in order, so that Q3 cubemap is seamless in openGL, but rotated & flipped; fix misorientation in shader later */ + const char *suffixes[] = { "_ft", "_bk", "_up", "_dn", "_rt", "_lf" }; + for( int i = 0; i < 6; ++i ){ + images[i] = key.first.loadImage( StringOutputStream( 64 )( key.second, suffixes[i] ) ); + } + if( std::all_of( images, images + std::size( images ), []( const Image *img ){ return img != nullptr; } ) ){ + glGenTextures( 1, &texture.texture_number ); + glBindTexture( GL_TEXTURE_CUBE_MAP, texture.texture_number ); + glTexParameteri( GL_TEXTURE_CUBE_MAP, GL_GENERATE_MIPMAP, GL_FALSE ); + + glTexParameteri( GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); + glTexParameteri( GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); + glTexParameteri( GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE ); + glTexParameteri( GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + glTexParameteri( GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + glTexParameteri( GL_TEXTURE_CUBE_MAP, GL_TEXTURE_BASE_LEVEL, 0 ); + glTexParameteri( GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_LEVEL, 0); //this or mipmaps are required for samplerCube to work + // fix non quadratic, varying sizes; GL_TEXTURE_CUBE_MAP requires this + unsigned int size = 0; + for( const auto img : images ) + size = std::max( { size, img->getWidth(), img->getHeight() } ); + for( int i = 0; i < 6; ++i ){ + const Image& img = *images[i]; + byte *pix = img.getRGBAPixels(); + if( img.getWidth() != size || img.getHeight() != size ){ + pix = static_cast( malloc( size * size * 4 ) ); + R_ResampleTexture( img.getRGBAPixels(), img.getWidth(), img.getHeight(), pix, size, size, 4 ); + } + glTexImage2D( GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, g_texture_globals.texture_components, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, pix ); + if( pix != img.getRGBAPixels() ) + free( pix ); + } + + glBindTexture( GL_TEXTURE_CUBE_MAP, 0 ); + globalOutputStream() << "Loaded Skybox: \"" << key.second << "\"\n"; + GlobalOpenGL_debugAssertNoErrors(); + } + else + { + globalErrorStream() << "Skybox load failed: \"" << key.second << "\"\n"; + } + + std::for_each_n( images, std::size( images ), []( Image *img ){ if( img != nullptr ) img->release(); } ); } } } diff --git a/radiant/xywindow.cpp b/radiant/xywindow.cpp index d5dc05eaa..6fe112417 100644 --- a/radiant/xywindow.cpp +++ b/radiant/xywindow.cpp @@ -1313,8 +1313,8 @@ void BackgroundImage::render( const VIEWTYPE viewtype ){ glDisable( GL_DEPTH_TEST ); glBindTexture( GL_TEXTURE_2D, _tex ); - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP ); - glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); glBegin( GL_QUADS ); diff --git a/setup/data/tools/gl/skybox_fp.glsl b/setup/data/tools/gl/skybox_fp.glsl new file mode 100644 index 000000000..6fa78f819 --- /dev/null +++ b/setup/data/tools/gl/skybox_fp.glsl @@ -0,0 +1,8 @@ + +uniform samplerCube skybox; + +void main() +{ + //doing rotation/flip to fix skybox orientation + gl_FragColor = textureCube( skybox, vec3( -gl_TexCoord[0].y, gl_TexCoord[0].z, gl_TexCoord[0].x ) ); +} \ No newline at end of file diff --git a/setup/data/tools/gl/skybox_vp.glsl b/setup/data/tools/gl/skybox_vp.glsl new file mode 100644 index 000000000..bb5486b7f --- /dev/null +++ b/setup/data/tools/gl/skybox_vp.glsl @@ -0,0 +1,8 @@ + +uniform vec3 u_view_origin; + +void main() +{ + gl_TexCoord[0] = vec4( ( gl_Vertex.xyz - u_view_origin ), 1 ); + gl_Position = ftransform(); +} \ No newline at end of file