2016-03-12 01:20:51 +01:00
/* Copyright (c) 2002-2012 Croteam Ltd.
This program is free software ; you can redistribute it and / or modify
it under the terms of version 2 of the GNU General Public License as published by
the Free Software Foundation
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License along
with this program ; if not , write to the Free Software Foundation , Inc . ,
51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 USA . */
2016-03-11 14:57:17 +01:00
2016-03-29 03:03:54 +02:00
# include "Engine/StdH.h"
2016-03-11 14:57:17 +01:00
# include <Engine/Graphics/GfxLibrary.h>
2016-03-29 03:03:54 +02:00
# include <Engine/Base/Statistics_Internal.h>
2016-03-11 14:57:17 +01:00
# include <Engine/Math/Functions.h>
# include <Engine/Graphics/Color.h>
2016-03-29 03:03:54 +02:00
# include <Engine/Graphics/OpenGL.h>
2016-03-11 14:57:17 +01:00
# include <Engine/Graphics/Texture.h>
# include <Engine/Graphics/GfxProfile.h>
# include <Engine/Base/ListIterator.inl>
// asm shortcuts
# define O offset
# define Q qword ptr
# define D dword ptr
# define W word ptr
# define B byte ptr
// we need array for OpenGL mipmaps that are lower than N*1 or 1*N
static ULONG _aulLastMipmaps [ ( INDEX ) ( 1024 * 1.334 ) ] ;
static CTexParams * _tpCurrent ;
extern INDEX GFX_iActiveTexUnit ;
// unpacks texture filering from one INDEX to two GLenums (and eventually re-adjust input INDEX)
static void UnpackFilter_OGL ( INDEX iFilter , GLenum & eMagFilter , GLenum & eMinFilter )
{
switch ( iFilter ) {
case 110 : case 10 : eMagFilter = GL_NEAREST ; eMinFilter = GL_NEAREST ; break ;
case 111 : case 11 : eMagFilter = GL_NEAREST ; eMinFilter = GL_NEAREST_MIPMAP_NEAREST ; break ;
case 112 : case 12 : eMagFilter = GL_NEAREST ; eMinFilter = GL_NEAREST_MIPMAP_LINEAR ; break ;
case 220 : case 20 : eMagFilter = GL_LINEAR ; eMinFilter = GL_LINEAR ; break ;
case 221 : case 21 : eMagFilter = GL_LINEAR ; eMinFilter = GL_LINEAR_MIPMAP_NEAREST ; break ;
case 222 : case 22 : eMagFilter = GL_LINEAR ; eMinFilter = GL_LINEAR_MIPMAP_LINEAR ; break ;
case 120 : eMagFilter = GL_NEAREST ; eMinFilter = GL_LINEAR ; break ;
case 121 : eMagFilter = GL_NEAREST ; eMinFilter = GL_LINEAR_MIPMAP_NEAREST ; break ;
case 122 : eMagFilter = GL_NEAREST ; eMinFilter = GL_LINEAR_MIPMAP_LINEAR ; break ;
case 210 : eMagFilter = GL_LINEAR ; eMinFilter = GL_NEAREST ; break ;
case 211 : eMagFilter = GL_LINEAR ; eMinFilter = GL_NEAREST_MIPMAP_NEAREST ; break ;
case 212 : eMagFilter = GL_LINEAR ; eMinFilter = GL_NEAREST_MIPMAP_LINEAR ; break ;
default : ASSERTALWAYS ( " Illegal OpenGL texture filtering mode. " ) ; break ;
}
}
// change texture filtering mode if needed
extern void MimicTexParams_OGL ( CTexParams & tpLocal )
{
ASSERT ( & tpLocal ! = NULL ) ;
_pfGfxProfile . StartTimer ( CGfxProfile : : PTI_TEXTUREPARAMS ) ;
// set texture filtering mode if required
if ( tpLocal . tp_iFilter ! = _tpGlobal [ 0 ] . tp_iFilter )
{ // update OpenGL texture filters
GLenum eMagFilter , eMinFilter ;
UnpackFilter_OGL ( _tpGlobal [ 0 ] . tp_iFilter , eMagFilter , eMinFilter ) ;
// adjust minimize filter in case of a single mipmap
if ( tpLocal . tp_bSingleMipmap ) {
if ( eMinFilter = = GL_NEAREST_MIPMAP_NEAREST | | eMinFilter = = GL_NEAREST_MIPMAP_LINEAR ) eMinFilter = GL_NEAREST ;
else if ( eMinFilter = = GL_LINEAR_MIPMAP_NEAREST | | eMinFilter = = GL_LINEAR_MIPMAP_LINEAR ) eMinFilter = GL_LINEAR ;
}
// update texture filter
pglTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MAG_FILTER , eMagFilter ) ;
pglTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MIN_FILTER , eMinFilter ) ;
tpLocal . tp_iFilter = _tpGlobal [ 0 ] . tp_iFilter ;
OGL_CHECKERROR ;
}
// set texture anisotropy degree if required and supported
if ( tpLocal . tp_iAnisotropy ! = _tpGlobal [ 0 ] . tp_iAnisotropy ) {
tpLocal . tp_iAnisotropy = _tpGlobal [ 0 ] . tp_iAnisotropy ;
if ( _pGfx - > gl_iMaxTextureAnisotropy > = 2 ) { // only if allowed
pglTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MAX_ANISOTROPY_EXT , tpLocal . tp_iAnisotropy ) ;
}
}
// set texture clamping modes if changed
if ( tpLocal . tp_eWrapU ! = _tpGlobal [ GFX_iActiveTexUnit ] . tp_eWrapU
| | tpLocal . tp_eWrapV ! = _tpGlobal [ GFX_iActiveTexUnit ] . tp_eWrapV )
{ // prepare temp vars
GLuint eWrapU = _tpGlobal [ GFX_iActiveTexUnit ] . tp_eWrapU = = GFX_REPEAT ? GL_REPEAT : GL_CLAMP ;
GLuint eWrapV = _tpGlobal [ GFX_iActiveTexUnit ] . tp_eWrapV = = GFX_REPEAT ? GL_REPEAT : GL_CLAMP ;
// eventually re-adjust clamping params in case of clamp_to_edge extension
if ( _pGfx - > gl_ulFlags & GLF_EXT_EDGECLAMP ) {
if ( eWrapU = = GL_CLAMP ) eWrapU = GL_CLAMP_TO_EDGE ;
if ( eWrapV = = GL_CLAMP ) eWrapV = GL_CLAMP_TO_EDGE ;
}
// set clamping params and update local texture clamping modes
pglTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_WRAP_S , eWrapU ) ;
pglTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_WRAP_T , eWrapV ) ;
tpLocal . tp_eWrapU = _tpGlobal [ GFX_iActiveTexUnit ] . tp_eWrapU ;
tpLocal . tp_eWrapV = _tpGlobal [ GFX_iActiveTexUnit ] . tp_eWrapV ;
OGL_CHECKERROR ;
}
// keep last texture params (for tex upload and stuff)
_tpCurrent = & tpLocal ;
_pfGfxProfile . StopTimer ( CGfxProfile : : PTI_TEXTUREPARAMS ) ;
}
// upload context for current texture to accelerator's memory
// (returns format in which texture was really uploaded)
2016-03-29 03:03:54 +02:00
void UploadTexture_OGL ( ULONG * pulTexture , PIX pixSizeU , PIX pixSizeV ,
2016-03-11 14:57:17 +01:00
GLenum eInternalFormat , BOOL bUseSubImage )
{
// safeties
ASSERT ( pulTexture ! = NULL ) ;
ASSERT ( pixSizeU > 0 & & pixSizeV > 0 ) ;
_sfStats . StartTimer ( CStatForm : : STI_BINDTEXTURE ) ;
_pfGfxProfile . StartTimer ( CGfxProfile : : PTI_TEXTUREUPLOADING ) ;
// upload each original mip-map
INDEX iMip = 0 ;
PIX pixOffset = 0 ;
while ( pixSizeU > 0 & & pixSizeV > 0 )
{
2016-04-07 05:19:40 +02:00
#if 0 // trust me, you'll know if it's not readable. :) This assert will read one past the start of the array if U or V is zero, so turning it off. --ryan.
2016-03-11 14:57:17 +01:00
// check that memory is readable
ASSERT ( pulTexture [ pixOffset + pixSizeU * pixSizeV - 1 ] ! = 0xDEADBEEF ) ;
2016-04-07 05:19:40 +02:00
# endif
2016-03-11 14:57:17 +01:00
// upload mipmap as fast as possible
if ( bUseSubImage ) {
pglTexSubImage2D ( GL_TEXTURE_2D , iMip , 0 , 0 , pixSizeU , pixSizeV ,
GL_RGBA , GL_UNSIGNED_BYTE , pulTexture + pixOffset ) ;
} else {
pglTexImage2D ( GL_TEXTURE_2D , iMip , eInternalFormat , pixSizeU , pixSizeV , 0 ,
GL_RGBA , GL_UNSIGNED_BYTE , pulTexture + pixOffset ) ;
} OGL_CHECKERROR ;
// advance to next mip-map
pixOffset + = pixSizeU * pixSizeV ;
pixSizeU > > = 1 ;
pixSizeV > > = 1 ;
iMip + + ;
// end here if there is only one mip-map to upload
if ( _tpCurrent - > tp_bSingleMipmap ) break ;
}
// see if we need to generate and upload additional mipmaps (those under 1*N or N*1)
if ( ! _tpCurrent - > tp_bSingleMipmap & & pixSizeU ! = pixSizeV )
{ // prepare variables
PIX pixSize = Max ( pixSizeU , pixSizeV ) ;
ASSERT ( pixSize < = 2048 ) ;
ULONG * pulSrc = pulTexture + pixOffset - pixSize * 2 ;
ULONG * pulDst = _aulLastMipmaps ;
// loop thru mipmaps
while ( pixSizeU > 0 | | pixSizeV > 0 )
{ // make next mipmap
if ( pixSizeU = = 0 ) pixSizeU = 1 ;
if ( pixSizeV = = 0 ) pixSizeV = 1 ;
pixSize = pixSizeU * pixSizeV ;
2016-03-29 03:03:54 +02:00
2016-04-24 19:16:04 +02:00
# if (defined __MSVC_INLINE__)
2016-03-11 14:57:17 +01:00
__asm {
pxor mm0 , mm0
mov esi , D [ pulSrc ]
mov edi , D [ pulDst ]
mov ecx , D [ pixSize ]
pixLoop :
movd mm1 , D [ esi + 0 ]
movd mm2 , D [ esi + 4 ]
punpcklbw mm1 , mm0
punpcklbw mm2 , mm0
paddw mm1 , mm2
psrlw mm1 , 1
packuswb mm1 , mm0
movd D [ edi ] , mm1
add esi , 4 * 2
add edi , 4
dec ecx
jnz pixLoop
emms
}
2016-03-29 03:03:54 +02:00
2016-04-24 16:38:22 +02:00
# elif (defined __GNU_INLINE_X86_32__)
2016-03-29 03:03:54 +02:00
__asm__ __volatile__ (
" pxor %%mm0,%%mm0 \n \t "
2016-04-19 03:21:13 +02:00
" movl %[pulSrc],%%esi \n \t "
" movl %[pulDst],%%edi \n \t "
" movl %[pixSize],%%ecx \n \t "
2016-03-29 03:03:54 +02:00
" 0: \n \t " // pixLoop
" movd 0(%%esi), %%mm1 \n \t "
" movd 4(%%esi), %%mm2 \n \t "
" punpcklbw %%mm0,%%mm1 \n \t "
" punpcklbw %%mm0,%%mm2 \n \t "
" paddw %%mm2,%%mm1 \n \t "
" psrlw $1,%%mm1 \n \t "
" packuswb %%mm0,%%mm1 \n \t "
" movd %%mm1, (%%edi) \n \t "
" addl $8,%%esi \n \t "
" addl $4,%%edi \n \t "
" decl %%ecx \n \t "
" jnz 0b \n \t " // pixLoop
" emms \n \t "
:
2016-04-19 03:21:13 +02:00
: [ pulSrc ] " g " ( pulSrc ) , [ pulDst ] " g " ( pulDst ) ,
[ pixSize ] " g " ( pixSize )
: FPU_REGS , " mm0 " , " mm1 " , " mm2 " ,
" ecx " , " esi " , " edi " , " memory " , " cc "
2016-03-29 03:03:54 +02:00
) ;
# else
2016-04-24 19:16:04 +02:00
// Basically average every other pixel...
UWORD w = 0 ;
UBYTE * dptr = ( UBYTE * ) pulDst ;
UBYTE * sptr = ( UBYTE * ) pulSrc ;
#if 0
pixSize * = 4 ;
for ( PIX i = 0 ; i < pixSize ; i + + )
{
* dptr = ( UBYTE ) ( ( ( ( UWORD ) sptr [ 0 ] ) + ( ( UWORD ) sptr [ 1 ] ) ) > > 1 ) ;
dptr + + ;
sptr + = 2 ;
}
# else
for ( PIX i = 0 ; i < pixSize ; i + + )
{
for ( PIX j = 0 ; j < 4 ; j + + )
{
* dptr = ( UBYTE ) ( ( ( ( UWORD ) sptr [ 0 ] ) + ( ( UWORD ) sptr [ 4 ] ) ) > > 1 ) ;
dptr + + ;
sptr + + ;
}
sptr + = 4 ;
}
# endif
2016-03-29 03:03:54 +02:00
# endif
2016-03-11 14:57:17 +01:00
// upload mipmap
if ( bUseSubImage ) {
pglTexSubImage2D ( GL_TEXTURE_2D , iMip , 0 , 0 , pixSizeU , pixSizeV ,
GL_RGBA , GL_UNSIGNED_BYTE , pulDst ) ;
} else {
pglTexImage2D ( GL_TEXTURE_2D , iMip , eInternalFormat , pixSizeU , pixSizeV , 0 ,
GL_RGBA , GL_UNSIGNED_BYTE , pulDst ) ;
} OGL_CHECKERROR ;
// advance to next mip-map
pulSrc = pulDst ;
pulDst + = pixSize ;
pixOffset + = pixSize ;
pixSizeU > > = 1 ;
pixSizeV > > = 1 ;
iMip + + ;
}
}
// all done
_pfGfxProfile . IncrementCounter ( CGfxProfile : : PCI_TEXTUREUPLOADS , 1 ) ;
_pfGfxProfile . IncrementCounter ( CGfxProfile : : PCI_TEXTUREUPLOADBYTES , pixOffset * 4 ) ;
_sfStats . IncrementCounter ( CStatForm : : SCI_TEXTUREUPLOADS , 1 ) ;
_sfStats . IncrementCounter ( CStatForm : : SCI_TEXTUREUPLOADBYTES , pixOffset * 4 ) ;
_pfGfxProfile . StopTimer ( CGfxProfile : : PTI_TEXTUREUPLOADING ) ;
_sfStats . StopTimer ( CStatForm : : STI_BINDTEXTURE ) ;
}
// returns bytes/pixels ratio for uploaded texture
extern INDEX GetFormatPixRatio_OGL ( GLenum eFormat )
{
switch ( eFormat ) {
case GL_RGBA :
case GL_RGBA8 :
return 4 ;
case GL_RGB :
case GL_RGB8 :
return 3 ;
case GL_RGB5 :
case GL_RGB5_A1 :
case GL_RGB4 :
case GL_RGBA4 :
case GL_LUMINANCE_ALPHA :
case GL_LUMINANCE8_ALPHA8 :
return 2 ;
// compressed formats and single-channel formats
default :
return 1 ;
}
}
// returns bytes/pixels ratio for uploaded texture
extern INDEX GetTexturePixRatio_OGL ( GLuint uiBindNo )
{
GLenum eInternalFormat ;
pglBindTexture ( GL_TEXTURE_2D , uiBindNo ) ;
pglGetTexLevelParameteriv ( GL_TEXTURE_2D , 0 , GL_TEXTURE_INTERNAL_FORMAT , ( GLint * ) & eInternalFormat ) ;
OGL_CHECKERROR ;
return GetFormatPixRatio_OGL ( eInternalFormat ) ;
}
// return allowed dithering method
extern INDEX AdjustDitheringType_OGL ( GLenum eFormat , INDEX iDitheringType )
{
switch ( eFormat ) {
// these formats don't need dithering
case GL_RGB8 :
case GL_RGBA8 :
case GL_LUMINANCE8 :
case GL_LUMINANCE8_ALPHA8 :
return NONE ;
// these formats need reduced dithering
case GL_RGB5 :
case GL_RGB5_A1 :
if ( iDitheringType > 7 ) return iDitheringType - 3 ;
// other formats need dithering as it is
default :
return iDitheringType ;
}
}