fixed depth fade with MSAA in the GL3 backend

This commit is contained in:
myT 2020-10-08 03:55:40 +02:00
parent 427172edcf
commit 78cf275a62
3 changed files with 143 additions and 99 deletions

View file

@ -7,6 +7,9 @@ DD Mmm 20 - 1.53
add: r_alphaToCoverageMipBoost <0.0 to 0.5> (default: 0.125) boosts the alpha value of higher mip levels add: r_alphaToCoverageMipBoost <0.0 to 0.5> (default: 0.125) boosts the alpha value of higher mip levels
with A2C enabled, it prevents alpha-tested surfaces from fading (too much) in the distance with A2C enabled, it prevents alpha-tested surfaces from fading (too much) in the distance
chg: with r_backend GL3, depth fade with MSAA now requires GLSL 4.00 at a minimum
depth access is now correctly multi-sampled (less aliasing) and performance has improved as well
chg: with r_backend GL3, alpha to coverage now requires GLSL 4.00 at a minimum chg: with r_backend GL3, alpha to coverage now requires GLSL 4.00 at a minimum
fix: with r_backend D3D11, some device reset scenarios caused fatal errors instead of video restarts fix: with r_backend D3D11, some device reset scenarios caused fatal errors instead of video restarts
@ -1002,12 +1005,6 @@ exp: not confirmed: broken barrier implementation?
fix: use D3D11 or upgrade drivers fix: use D3D11 or upgrade drivers
act: can't fix (root cause unknown) act: can't fix (root cause unknown)
iss: with GL3, depth faded surfaces break anti-aliasing
exp: can't fetch from the right sample index because render buffers
are used as FBO attachments instead of textures
fix: use D3D11
act: will get fixed
iss: strong gamma correction breaks anti-aliasing iss: strong gamma correction breaks anti-aliasing
exp: MSAA resolve happens before the post-process pass exp: MSAA resolve happens before the post-process pass
act: will get fixed for D3D11 and GL3 act: will get fixed for D3D11 and GL3

View file

@ -52,7 +52,6 @@ static void GAL_Begin2D();
static GLint GetTexEnv( texEnv_t texEnv ); static GLint GetTexEnv( texEnv_t texEnv );
void GL_GetRenderTargetFormat( GLenum* internalFormat, GLenum* format, GLenum* type, int cnq3Format ); void GL_GetRenderTargetFormat( GLenum* internalFormat, GLenum* format, GLenum* type, int cnq3Format );
void GL_CreateColorRenderBufferStorageMS( int* samples );
struct GLSL_Program { struct GLSL_Program {
@ -334,8 +333,8 @@ static const char* dynLightFS =
struct FrameBuffer { struct FrameBuffer {
GLuint fbo; GLuint fbo;
GLuint color; // texture if MS, buffer if SS GLuint color; // texture if SS, renderbuffer if MS
GLuint depthStencil; // texture if MS, buffer if SS GLuint depthStencil; // texture if SS, renderbuffer if MS
qbool multiSampled; qbool multiSampled;
qbool hasDepthStencil; qbool hasDepthStencil;
}; };
@ -390,6 +389,41 @@ static void GL2_CheckError( const char* call, const char* function, const char*
} }
static void GL2_CreateColorRenderBufferStorageMS( int* samples )
{
GLenum internalFormat, format, type;
GL_GetRenderTargetFormat( &internalFormat, &format, &type, r_rtColorFormat->integer );
int sampleCount = r_msaa->integer;
while ( glGetError() != GL_NO_ERROR ) {} // clear the error queue
if ( GLEW_VERSION_4_2 || GLEW_ARB_internalformat_query )
{
GLint maxSampleCount = 0;
glGetInternalformativ( GL_RENDERBUFFER, internalFormat, GL_SAMPLES, 1, &maxSampleCount );
if ( glGetError() == GL_NO_ERROR )
sampleCount = min(sampleCount, (int)maxSampleCount);
}
GLenum errorCode = GL_NO_ERROR;
for ( ;; )
{
// @NOTE: when the sample count is invalid, the error code is GL_INVALID_OPERATION
glRenderbufferStorageMultisample( GL_RENDERBUFFER, sampleCount, internalFormat, glConfig.vidWidth, glConfig.vidHeight );
errorCode = glGetError();
if ( errorCode == GL_NO_ERROR || sampleCount == 0 )
break;
--sampleCount;
}
if ( errorCode != GL_NO_ERROR )
ri.Error( ERR_FATAL, "Failed to create multi-sampled render buffer storage (error 0x%X)\n", (unsigned int)errorCode );
*samples = sampleCount;
}
static qbool GL2_FBO_CreateSS( FrameBuffer& fb, qbool depthStencil ) static qbool GL2_FBO_CreateSS( FrameBuffer& fb, qbool depthStencil )
{ {
while ( glGetError() != GL_NO_ERROR ) {} // clear the error queue while ( glGetError() != GL_NO_ERROR ) {} // clear the error queue
@ -441,7 +475,7 @@ static qbool GL2_FBO_CreateMS( int* sampleCount, FrameBuffer& fb )
GL(glGenRenderbuffers( 1, &fb.color )); GL(glGenRenderbuffers( 1, &fb.color ));
GL(glBindRenderbuffer( GL_RENDERBUFFER, fb.color )); GL(glBindRenderbuffer( GL_RENDERBUFFER, fb.color ));
GL_CreateColorRenderBufferStorageMS( sampleCount ); GL2_CreateColorRenderBufferStorageMS( sampleCount );
GL(glFramebufferRenderbuffer( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, fb.color )); GL(glFramebufferRenderbuffer( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, fb.color ));
GL(glGenRenderbuffers( 1, &fb.depthStencil )); GL(glGenRenderbuffers( 1, &fb.depthStencil ));

View file

@ -35,10 +35,11 @@ along with Challenge Quake 3. If not, see <https://www.gnu.org/licenses/>.
Current info: Current info:
- OpenGL 3.2 minimum - OpenGL 3.2 minimum
- GLSL 1.40 minimum - GLSL 1.40 minimum
- fancy mip-map generations requires: - fancy mip-map generation requires:
- OpenGL 4.3 (or equivalent extensions) - OpenGL 4.3 (or equivalent extensions)
- GLSL 4.30 - GLSL 4.30
- alpha to coverage requires GLSL 4.00 - alpha to coverage requires GLSL 4.00
- depth fade with MSAA requires GLSL 4.00
Vertex and index data streaming notes: Vertex and index data streaming notes:
- everyone: persistent coherent buffer mapping is the best option whenever available - everyone: persistent coherent buffer mapping is the best option whenever available
@ -137,8 +138,8 @@ struct PipelineArrayBuffer
struct FrameBuffer struct FrameBuffer
{ {
GLuint fbo; GLuint fbo;
GLuint color; // texture if MS, buffer if SS GLuint color;
GLuint depthStencil; // texture if MS, buffer if SS GLuint depthStencil;
qbool multiSampled; qbool multiSampled;
qbool hasDepthStencil; qbool hasDepthStencil;
qbool hasColor; qbool hasColor;
@ -260,7 +261,6 @@ struct OpenGL3
qbool enableAlphaToCoverage; qbool enableAlphaToCoverage;
FrameBuffer fbMS; FrameBuffer fbMS;
FrameBuffer fbSSDepth; // resolved depth/stencil from fbMS
FrameBuffer fbSS[2]; FrameBuffer fbSS[2];
unsigned int fbReadIndex; // indexes fbSS unsigned int fbReadIndex; // indexes fbSS
qbool fbMSEnabled; qbool fbMSEnabled;
@ -503,7 +503,11 @@ static const char* sprite_vs =
static const char* sprite_fs = static const char* sprite_fs =
"uniform sampler2D texture1; // diffuse texture\n" "uniform sampler2D texture1; // diffuse texture\n"
"#if CNQ3_MSAA\n"
"uniform sampler2DMS texture2; // depth texture\n"
"#else\n"
"uniform sampler2D texture2; // depth texture\n" "uniform sampler2D texture2; // depth texture\n"
"#endif\n"
"\n" "\n"
"uniform uint alphaTest;\n" "uniform uint alphaTest;\n"
"uniform vec2 distOffset;\n" "uniform vec2 distOffset;\n"
@ -543,7 +547,11 @@ static const char* sprite_fs =
" (alphaTest == uint(3) && r.a < 0.5))\n" " (alphaTest == uint(3) && r.a < 0.5))\n"
" discard;\n" " discard;\n"
"\n" "\n"
"#if CNQ3_MSAA\n"
" float depthSRaw = texelFetch(texture2, ivec2(gl_FragCoord.xy), gl_SampleID).r;\n"
"#else\n"
" float depthSRaw = texelFetch(texture2, ivec2(gl_FragCoord.xy), 0).r;\n" " float depthSRaw = texelFetch(texture2, ivec2(gl_FragCoord.xy), 0).r;\n"
"#endif\n"
" float depthS = LinearDepth(depthSRaw * 2.0 - 1.0);\n" " float depthS = LinearDepth(depthSRaw * 2.0 - 1.0);\n"
" float depthP = depthVS - offset;\n" " float depthP = depthVS - offset;\n"
" float scale = Contrast((depthS - depthP) * distance, 2.0);\n" " float scale = Contrast((depthS - depthP) * distance, 2.0);\n"
@ -693,46 +701,6 @@ void GL_GetRenderTargetFormat(GLenum* internalFormat, GLenum* format, GLenum* ty
} }
} }
void GL_CreateColorRenderBufferStorageMS(int* samples)
{
GLenum internalFormat, format, type;
GL_GetRenderTargetFormat(&internalFormat, &format, &type, r_rtColorFormat->integer);
int sampleCount = r_msaa->integer;
while(glGetError() != GL_NO_ERROR) {} // clear the error queue
if(GLEW_VERSION_4_2 || GLEW_ARB_internalformat_query)
{
GLint maxSampleCount = 0;
glGetInternalformativ(GL_RENDERBUFFER, internalFormat, GL_SAMPLES, 1, &maxSampleCount);
if(glGetError() == GL_NO_ERROR)
{
sampleCount = min(sampleCount, (int)maxSampleCount);
}
}
GLenum errorCode = GL_NO_ERROR;
for(;;)
{
// @NOTE: when the sample count is invalid, the error code is GL_INVALID_OPERATION
glRenderbufferStorageMultisample(GL_RENDERBUFFER, sampleCount, internalFormat, glConfig.vidWidth, glConfig.vidHeight);
errorCode = glGetError();
if(errorCode == GL_NO_ERROR || sampleCount == 0)
{
break;
}
--sampleCount;
}
if(errorCode != GL_NO_ERROR)
{
ri.Error(ERR_FATAL, "Failed to create multi-sampled render buffer storage (error 0x%X)\n", (unsigned int)errorCode);
}
*samples = sampleCount;
}
#if defined(_WIN32) #if defined(_WIN32)
static void AllocatePinnedMemory(ArrayBuffer* buffer) static void AllocatePinnedMemory(ArrayBuffer* buffer)
@ -805,7 +773,8 @@ static const char* GetShaderTypeName(GLenum shaderType)
static qbool CreateShader(GLuint* shaderPtr, PipelineId pipelineId, GLenum shaderType, const char* shaderSource, const char* debugName) static qbool CreateShader(GLuint* shaderPtr, PipelineId pipelineId, GLenum shaderType, const char* shaderSource, const char* debugName)
{ {
// A2C now requires GLSL 4.00 for textureQueryLod // alpha to coverage now requires GLSL 4.00 for textureQueryLod
// depth fade with MSAA now requires GLSL 4.00 for gl_SampleID
const qbool enableA2C = const qbool enableA2C =
pipelineId == PID_GENERIC && pipelineId == PID_GENERIC &&
shaderType == GL_FRAGMENT_SHADER && shaderType == GL_FRAGMENT_SHADER &&
@ -814,12 +783,18 @@ static qbool CreateShader(GLuint* shaderPtr, PipelineId pipelineId, GLenum shade
pipelineId == PID_GENERIC && pipelineId == PID_GENERIC &&
shaderType == GL_FRAGMENT_SHADER && shaderType == GL_FRAGMENT_SHADER &&
r_dither->integer; r_dither->integer;
const char* sourceArray[] = const qbool depthFadeWithMSAA =
pipelineId == PID_SOFT_SPRITE &&
shaderType == GL_FRAGMENT_SHADER &&
glInfo.depthFadeSupport &&
gl.fbMSEnabled;
const char* const sourceArray[] =
{ {
shaderType == GL_COMPUTE_SHADER ? "#version 430\n" : (enableA2C ? "#version 400\n" : "#version 140\n"), shaderType == GL_COMPUTE_SHADER ? "#version 430\n" : (enableA2C || depthFadeWithMSAA ? "#version 400\n" : "#version 140\n"),
"\n", "\n",
enableA2C ? "#define CNQ3_A2C 1\n" : "#define CNQ3_A2C 0\n", enableA2C ? "#define CNQ3_A2C 1\n" : "#define CNQ3_A2C 0\n",
enableDithering ? "#define CNQ3_DITHER 1\n" : "#define CNQ3_DITHER 0\n", enableDithering ? "#define CNQ3_DITHER 1\n" : "#define CNQ3_DITHER 0\n",
depthFadeWithMSAA ? "#define CNQ3_MSAA 1\n" : "#define CNQ3_MSAA 0\n",
shaderSource shaderSource
}; };
@ -930,7 +905,44 @@ static qbool CreateComputeProgram(Program* prog, const char* cs, const char* deb
return FinalizeProgram(prog, debugName); return FinalizeProgram(prog, debugName);
} }
extern void GL_GetRenderTargetFormat(GLenum* internalFormat, GLenum* format, GLenum* type, int cnq3Format); static void CreateColorTextureStorageMS(int* samples)
{
GLenum internalFormat, format, type;
GL_GetRenderTargetFormat(&internalFormat, &format, &type, r_rtColorFormat->integer);
int sampleCount = r_msaa->integer;
while(glGetError() != GL_NO_ERROR) {} // clear the error queue
if(GLEW_VERSION_4_2 || GLEW_ARB_internalformat_query)
{
GLint maxSampleCount = 0;
glGetInternalformativ(GL_TEXTURE_2D_MULTISAMPLE, internalFormat, GL_SAMPLES, 1, &maxSampleCount);
if(glGetError() == GL_NO_ERROR)
{
sampleCount = min(sampleCount, (int)maxSampleCount);
}
}
GLenum errorCode = GL_NO_ERROR;
for(;;)
{
glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, sampleCount, internalFormat, glConfig.vidWidth, glConfig.vidHeight, GL_TRUE);
errorCode = glGetError();
if(errorCode == GL_NO_ERROR || sampleCount == 0)
{
break;
}
--sampleCount;
}
if(errorCode != GL_NO_ERROR)
{
ri.Error(ERR_FATAL, "Failed to create multi-sampled texture storage (error 0x%X)\n", (unsigned int)errorCode);
}
*samples = sampleCount;
}
static void FBO_CreateSS(FrameBuffer* fb, qbool color, qbool depthStencil, const char* name) static void FBO_CreateSS(FrameBuffer* fb, qbool color, qbool depthStencil, const char* name)
{ {
@ -986,17 +998,17 @@ static void FBO_CreateMS(int* sampleCount, FrameBuffer* fb, const char* name)
glGenFramebuffers(1, &fb->fbo); glGenFramebuffers(1, &fb->fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fb->fbo); glBindFramebuffer(GL_FRAMEBUFFER, fb->fbo);
glGenRenderbuffers(1, &fb->color); glGenTextures(1, &fb->color);
glBindRenderbuffer(GL_RENDERBUFFER, fb->color); glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, fb->color);
GL_CreateColorRenderBufferStorageMS(sampleCount); CreateColorTextureStorageMS(sampleCount);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, fb->color); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, fb->color, 0);
SetDebugName(GL_RENDERBUFFER, fb->color, va("%s color attachment 0", name)); SetDebugName(GL_TEXTURE, fb->color, va("%s color attachment 0", name));
glGenRenderbuffers(1, &fb->depthStencil); glGenTextures(1, &fb->depthStencil);
glBindRenderbuffer(GL_RENDERBUFFER, fb->depthStencil); glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, fb->depthStencil);
glRenderbufferStorageMultisample(GL_RENDERBUFFER, *sampleCount, GL_DEPTH24_STENCIL8, glConfig.vidWidth, glConfig.vidHeight); glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, *sampleCount, GL_DEPTH24_STENCIL8, glConfig.vidWidth, glConfig.vidHeight, GL_TRUE);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fb->depthStencil); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D_MULTISAMPLE, fb->depthStencil, 0);
SetDebugName(GL_RENDERBUFFER, fb->depthStencil, va("%s depth/stencil attachment", name)); SetDebugName(GL_TEXTURE, fb->depthStencil, va("%s depth/stencil attachment", name));
const GLenum fboStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER); const GLenum fboStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if(fboStatus != GL_FRAMEBUFFER_COMPLETE) if(fboStatus != GL_FRAMEBUFFER_COMPLETE)
@ -1020,7 +1032,6 @@ static void FBO_Init()
if(gl.fbMSEnabled) if(gl.fbMSEnabled)
{ {
FBO_CreateMS(&finalSampleCount, &gl.fbMS, "main"); FBO_CreateMS(&finalSampleCount, &gl.fbMS, "main");
FBO_CreateSS(&gl.fbSSDepth, qfalse, qtrue, "depth resolve");
FBO_CreateSS(&gl.fbSS[0], qtrue, qfalse, "post-process #1"); FBO_CreateSS(&gl.fbSS[0], qtrue, qfalse, "post-process #1");
FBO_CreateSS(&gl.fbSS[1], qtrue, qfalse, "post-process #2"); FBO_CreateSS(&gl.fbSS[1], qtrue, qfalse, "post-process #2");
} }
@ -1115,18 +1126,6 @@ static void FBO_ResolveColor()
glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_LINEAR); glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_LINEAR);
} }
static void FBO_ResolveDepth()
{
const FrameBuffer& r = gl.fbMS;
const FrameBuffer& d = gl.fbSSDepth;
glBindFramebuffer(GL_READ_FRAMEBUFFER, r.fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, d.fbo);
const int w = glConfig.vidWidth;
const int h = glConfig.vidHeight;
glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT, GL_NEAREST);
}
static void ApplyPipeline(PipelineId pipelineId) static void ApplyPipeline(PipelineId pipelineId)
{ {
if(pipelineId == gl.pipelineId) if(pipelineId == gl.pipelineId)
@ -1134,6 +1133,17 @@ static void ApplyPipeline(PipelineId pipelineId)
return; return;
} }
// The depth fade pipeline is the only one reading from the depth texture
// but doesn't write to it.
// Any change to that pipeline requires a texture barrier with OpenGL 4.5+
// to make sure we get valid data when reading the depth texture.
// See "Feedback Loops Between Textures and the Framebuffer" in the specs.
if((GLEW_VERSION_4_5 || GLEW_ARB_texture_barrier) &&
pipelineId == PID_SOFT_SPRITE)
{
glTextureBarrier();
}
gl.pipelineId = pipelineId; gl.pipelineId = pipelineId;
Pipeline* const pipeline = &gl.pipelines[pipelineId]; Pipeline* const pipeline = &gl.pipelines[pipelineId];
@ -1154,20 +1164,14 @@ static void ApplyPipeline(PipelineId pipelineId)
} }
} }
if(pipelineId == PID_SOFT_SPRITE && gl.fbMSEnabled)
{
// This is not how it should be done and will counter the benefits of MSAA.
// To do this right, we need to bind the FBO's depth attachment to the shader and for that,
// we need multi-sampled textures as FBO attachments instead of multi-sampled render buffers.
// We also need the shader to use gl_SampleID, which changes our minimum requirements.
// Because of all these changes and lack of testing time,
// I'll do the necessary changes after the 1.52 release to avoid problems.
FBO_ResolveDepth();
FBO_Bind();
}
glUniform1i(pipeline->textureLocations[0], 0); glUniform1i(pipeline->textureLocations[0], 0);
glActiveTexture(GL_TEXTURE1); glActiveTexture(GL_TEXTURE1);
if(pipelineId == PID_SOFT_SPRITE && gl.fbMSEnabled)
{
// we don't have a "BindTextureMS" function for caching/tracking MS texture binds
// since this is the only one we read from a fragment shader at the moment
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, gl.fbMS.depthStencil);
}
glUniform1i(pipeline->textureLocations[1], 1); glUniform1i(pipeline->textureLocations[1], 1);
glActiveTexture(GL_TEXTURE0); glActiveTexture(GL_TEXTURE0);
@ -1862,6 +1866,7 @@ static void Init()
gl.maxTextureSize = maxTextureSize > 0 ? min((int)maxTextureSize, MAX_GPU_TEXTURE_SIZE) : MAX_GPU_TEXTURE_SIZE; gl.maxTextureSize = maxTextureSize > 0 ? min((int)maxTextureSize, MAX_GPU_TEXTURE_SIZE) : MAX_GPU_TEXTURE_SIZE;
glConfig.unused_maxTextureSize = gl.maxTextureSize; glConfig.unused_maxTextureSize = gl.maxTextureSize;
glInfo.maxTextureSize = gl.maxTextureSize; glInfo.maxTextureSize = gl.maxTextureSize;
glInfo.depthFadeSupport = r_depthFade->integer == 1;
FBO_Init(); FBO_Init();
if(gl.fbMSEnabled && r_alphaToCoverage->integer) if(gl.fbMSEnabled && r_alphaToCoverage->integer)
@ -1998,8 +2003,6 @@ static void Init()
gl.errorMode = EM_FATAL; gl.errorMode = EM_FATAL;
} }
glInfo.depthFadeSupport = r_depthFade->integer == 1;
gl.pipelineId = PID_COUNT; gl.pipelineId = PID_COUNT;
ApplyPipeline(PID_GENERIC); ApplyPipeline(PID_GENERIC);
@ -2359,6 +2362,13 @@ static void DrawDepthFade()
{ {
const shaderStage_t* stage = tess.xstages[i]; const shaderStage_t* stage = tess.xstages[i];
// We have already made sure (in theory) we won't have depth writes enabled
// to avoid "feedback loops" on the depth texture, resulting in undefined behavior.
// See "Feedback Loops Between Textures and the Framebuffer" in the GL specs.
// However, this is not enough for OpenGL 4.5+, where glTextureBarrier is needed too
// because caching means a feedback loop can happen across draw calls.
assert((stage->stateBits & GLS_DEPTHTEST_DISABLE) == 0);
ApplyState(stage->stateBits, tess.shader->cullType, tess.shader->polygonOffset); ApplyState(stage->stateBits, tess.shader->cullType, tess.shader->polygonOffset);
UploadVertexArray(VB_TEXCOORD, tess.svars[i].texcoordsptr); UploadVertexArray(VB_TEXCOORD, tess.svars[i].texcoordsptr);
@ -2372,9 +2382,12 @@ static void DrawDepthFade()
} }
BindBundle(0, &stage->bundle); BindBundle(0, &stage->bundle);
glActiveTexture(GL_TEXTURE1); if(!gl.fbMSEnabled)
BindTexture(1, gl.fbMSEnabled ? gl.fbSSDepth.depthStencil : gl.fbSS[gl.fbReadIndex].depthStencil); {
glActiveTexture(GL_TEXTURE0); glActiveTexture(GL_TEXTURE1);
BindTexture(1, gl.fbSS[gl.fbReadIndex].depthStencil);
glActiveTexture(GL_TEXTURE0);
}
DrawElements(tess.numIndexes); DrawElements(tess.numIndexes);
} }