A texture in GLES means:
a texture data + sampler state, which is directly bound able to the pipeline.
You’re looking at it wrong.
In OpenGL ES you need to “emulate” samplers by allowing the user to set sample states on the device (via your own sampler structures you provide through your library) which, like in any other API, can be set at any time. All it does is set a pointer or make a copy. The same as when you set a texture. Nothing happens until the moment you are ready to render.
When it comes time to perform a render, first all the textures that were told to be set are “officially” set in OpenGL ES.
Then your graphics device will go over all the samplers that have been set (remembering that this is sampler data inside your custom structure).
You basically create a structure for OpenGL ES that matches exactly D3D11_SAMPLER_DESC and call ::glTexParameteri() and friends each frame.
Direct3D 11:
[spoiler]
LSVOID LSE_CALL CDirectX11::ApplyRenderStates( LSBOOL _bForce ) {
const LSG_RENDER_STATE & rsCurState = m_rsCurRenderState;
LSG_RENDER_STATE & rsLastState = m_rsLastRenderState;
#define LSG_CHANGED( MEMBER ) ((rsCurState.MEMBER != rsLastState.MEMBER) || _bForce)
#define LSG_UPDATE( MEMBER ) rsLastState.MEMBER = rsCurState.MEMBER
// Sampler states.
LSUINT32 ui32Index = 0, ui32Total = 0;
LSUINT32 ui32Max = LSG_MAX_SHADER_SAMPLERS;
for ( LSUINT32 I = 0; I <= ui32Max; ++I ) {
// Only update what has changed since the last render call.
if ( I < ui32Max && LSG_CHANGED( pssSamplers[I] ) ) {
++ui32Total;
LSG_UPDATE( pssSamplers[I] );
}
else {
if ( ui32Total ) {
// Simulate OpenGL ES 2.0 by setting samplers on all shader systems.
GetDirectX11Context()->CSSetSamplers( ui32Index, ui32Total, &rsCurState.pssSamplers[ui32Index] );
GetDirectX11Context()->DSSetSamplers( ui32Index, ui32Total, &rsCurState.pssSamplers[ui32Index] );
GetDirectX11Context()->HSSetSamplers( ui32Index, ui32Total, &rsCurState.pssSamplers[ui32Index] );
GetDirectX11Context()->GSSetSamplers( ui32Index, ui32Total, &rsCurState.pssSamplers[ui32Index] );
GetDirectX11Context()->PSSetSamplers( ui32Index, ui32Total, &rsCurState.pssSamplers[ui32Index] );
GetDirectX11Context()->VSSetSamplers( ui32Index, ui32Total, &rsCurState.pssSamplers[ui32Index] );
}
ui32Index = I + 1;
ui32Total = 0;
}
}
…
// Textures.
ui32Index = 0;
ui32Total = 0;
ui32Max = CGfxBase::m_mMetrics.ui32MaxTexSlot;
for ( LSUINT32 I = 0; I <= ui32Max; ++I ) {
// Only update what has changed since the last render call.
if ( I < ui32Max && LSG_CHANGED( psrShaderResources[I] ) ) {
++ui32Total;
LSG_UPDATE( psrShaderResources[I] );
}
else {
if ( ui32Total ) {
// Simulate OpenGL ES 2.0 by setting textures on all shader systems.
GetDirectX11Context()->CSSetShaderResources( ui32Index, ui32Total, &rsCurState.psrShaderResources[ui32Index] );
GetDirectX11Context()->DSSetShaderResources( ui32Index, ui32Total, &rsCurState.psrShaderResources[ui32Index] );
GetDirectX11Context()->HSSetShaderResources( ui32Index, ui32Total, &rsCurState.psrShaderResources[ui32Index] );
GetDirectX11Context()->GSSetShaderResources( ui32Index, ui32Total, &rsCurState.psrShaderResources[ui32Index] );
GetDirectX11Context()->PSSetShaderResources( ui32Index, ui32Total, &rsCurState.psrShaderResources[ui32Index] );
GetDirectX11Context()->VSSetShaderResources( ui32Index, ui32Total, &rsCurState.psrShaderResources[ui32Index] );
}
ui32Index = I + 1;
ui32Total = 0;
}
}
#undef LSG_UPDATE
#undef LSG_CHANGED
}
[/spoiler]
OpenGL ES 2.0:
[spoiler]
/** Sampler description. */
typedef struct LSG_SAMPLER_DESC {
LSG_FILTER Filter; /**< Filtering method to use when sampling a texture. */
LSG_TEXTURE_ADDRESS_MODE AddressU; /**< Method to use for resolving a u texture coordinate that is outside the 0 to 1 range. */
LSG_TEXTURE_ADDRESS_MODE AddressV; /**< Method to use for resolving a v texture coordinate that is outside the 0 to 1 range. */
LSG_TEXTURE_ADDRESS_MODE AddressW; /**< Method to use for resolving a w texture coordinate that is outside the 0 to 1 range. */
LSGREAL MipLODBias; /**< Offset from the calculated mipmap level. */
LSUINT32 MaxAnisotropy; /**< Clamping value used if LSG_F_ANISOTROPIC is specified in Filter. */
LSG_COMPARISON_FUNC ComparisonFunc; /**< A function that compares sampled data against existing sampled data. */
LSGREAL BorderColor[4]; /**< Border color to use if LSG_TAM_BORDER is specified for AddressU, AddressV, or AddressW. Range must be between 0.0 and 1.0 inclusive. */
LSGREAL MinLOD; /**< Lower end of the mipmap range to clamp access to, where 0 is the largest and most detailed mipmap level and any level higher than that is less detailed. */
LSGREAL MaxLOD; /**< Upper end of the mipmap range to clamp access to, where 0 is the largest and most detailed mipmap level and any level higher than that is less detailed. This value must be greater than or equal to MinLOD. To have no upper limit on LOD set this to a large value. */
} * LPLSG_SAMPLER_DESC, * const LPCLSG_SAMPLER_DESC;
LSVOID LSE_CALL COpenGlEs::ApplyRenderStates( LSBOOL _bForce ) {
const LSG_RENDER_STATE & rsCurState = m_rsCurRenderState;
LSG_RENDER_STATE & rsLastState = m_rsLastRenderState;
#define LSG_CHANGED( MEMBER ) ((rsCurState.MEMBER != rsLastState.MEMBER) || _bForce)
#define LSG_UPDATE( MEMBER ) rsLastState.MEMBER = rsCurState.MEMBER
…
// Textures. Must set before samplers.
for ( LSUINT32 I = LSE_ELEMENTS( rsCurState.psrShaderResources ); I--; ) {
if ( LSG_CHANGED( psrShaderResources[I] ) ) {
ActivateTexture( I );
if ( !rsCurState.psrShaderResources[I] ) {
::glBindTexture( GL_TEXTURE_2D, 0 );
}
else {
::glBindTexture( GL_TEXTURE_2D, (*rsCurState.psrShaderResources[I]) );
}
LSG_UPDATE( psrShaderResources[I] );
}
}
// Basic states.
SetSamplerStates( _bForce ); // Must be done after textures are set.
#undef LSG_UPDATE
#undef LSG_CHANGED
}
LSE_INLINE LSVOID LSE_FCALL COpenGlEs::SetSamplerStates( LSBOOL _bForce ) {
for ( LSUINT32 I = m_mMetrics.ui32MaxTexSlot; I--; ) {
SetSamplerState( I, _bForce );
}
}
LSE_INLINE LSVOID LSE_FCALL COpenGlEs::SetSamplerState( LSUINT32 _ui32Slot, LSBOOL _bForce ) {
// If no texture set and not forced, do nothing.
if ( !_bForce ) {
if ( !m_rsCurRenderState.psrShaderResources[_ui32Slot] ) { return; }
}
glWarnError( "Uncaught" );
if ( !m_rsCurRenderState.pssSamplers[_ui32Slot] ) {
m_rsCurRenderState.pssSamplers[_ui32Slot] = m_pssDefaultSamplerState;
}
ActivateTexture( _ui32Slot );
const LSG_SAMPLER_STATE & ssCurState = (*m_rsCurRenderState.pssSamplers[_ui32Slot]);
//LSG_SAMPLER_STATE & ssLastState = m_ssLastSamplers[_ui32Slot];
/*#define LSG_CHANGED( MEMBER ) ((ssCurState.MEMBER != ssLastState.MEMBER) || _bForce)
#define LSG_UPDATE( MEMBER ) ssLastState.MEMBER = ssCurState.MEMBER*/
static const GLint iMinFilter[] = {
GL_NEAREST, // Point, no mipmaps.
GL_NEAREST_MIPMAP_NEAREST, // Point, point mipmaps.
GL_NEAREST_MIPMAP_LINEAR, // Point, linear mipmaps.
GL_LINEAR, // Linear, no mipmaps.
GL_LINEAR_MIPMAP_NEAREST, // Linear, point mipmaps.
GL_LINEAR_MIPMAP_LINEAR, // Linear, linear mipmaps.
};
static const GLint iMagFilter[] = {
GL_NEAREST, // Point.
GL_LINEAR, // Linear.
};
// Always set filters because they are a property of textures.
// Min filter.
LSUINT32 ui32CurMinFilter = LSG_DECODE_MIN_FILTER( ssCurState.Filter );
// Mag filter.
LSUINT32 ui32CurMagFilter = LSG_DECODE_MAG_FILTER( ssCurState.Filter );
// Mip filter.
//LSUINT32 ui32CurMipFilter = LSG_DECODE_MIP_FILTER( ssCurState.Filter );
LSUINT32 ui32OglMinFilter = ui32CurMinFilter * 3; // TODO: Add mipmap filter.
LSUINT32 ui32OglMagFilter = ui32CurMagFilter;
::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, iMinFilter[ui32OglMinFilter] );
glWarnError( "GL_TEXTURE_MIN_FILTER" );
::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, iMagFilter[ui32OglMagFilter] );
glWarnError( "GL_TEXTURE_MAG_FILTER" );
// Anisotropy.
LSBOOL bCurIsAniso = LSG_DECODE_IS_ANISOTROPIC_FILTER( ssCurState.Filter );
if ( !bCurIsAniso ) {
::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1 );
}
else {
::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, CStd::Min<LSUINT32>( ssCurState.MaxAnisotropy, CGfxBase::m_mMetrics.ui32MaxAniso ) );
}
glWarnError( "GL_TEXTURE_MAX_ANISOTROPY_EXT" );
// Address modes.
::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, ssCurState.AddressU );
glWarnError( "GL_TEXTURE_WRAP_S" );
::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, ssCurState.AddressV );
glWarnError( "GL_TEXTURE_WRAP_T" );
/*::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, ssCurState.AddressW );
glWarnError( "GL_TEXTURE_WRAP_R" );
// LOD bias.
::glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, ssCurState.MipLODBias );
glWarnError( "GL_TEXTURE_LOD_BIAS" );
// Border color.
::glTexParameterfv( GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, ssCurState.BorderColor );
glWarnError( "GL_TEXTURE_BORDER_COLOR" );
// Minimum/maximum mip-map level.
::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, static_cast<GLint>(ssCurState.MinLOD) );
glWarnError( "GL_TEXTURE_BASE_LEVEL" );
::glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, static_cast<GLint>(ssCurState.MaxLOD) );
glWarnError( "GL_TEXTURE_MAX_LEVEL" );*/
/*#undef LSG_UPDATE
#undef LSG_CHANGED*/
}
[/spoiler]
You have effectively added samplers to OpenGL ES.
The overhead in always setting sampler-state information can be minimized by keeping track per-texture what sampler states have been set and then not re-applying them, but I have omitted that here for simplicity’s sake.
Additionally, you should not shy away from emulating proper samplers in OpenGL ES because even Khronos admits that strongly associating them with textures was a mistake and as of OpenGL 3.3 they provide
proper samplers.
L. Spiro