diff --git a/src/engine/renderer/GLUtils.h b/src/engine/renderer/GLUtils.h index 5918e0f2e0..f06d0eb875 100644 --- a/src/engine/renderer/GLUtils.h +++ b/src/engine/renderer/GLUtils.h @@ -78,6 +78,8 @@ struct GLConfig int max3DTextureSize; int maxCubeMapTextureSize; int maxTextureUnits; + int maxColorTextureSamples; + int maxDepthTextureSamples; char shadingLanguageVersionString[MAX_STRING_CHARS]; int shadingLanguageVersion; @@ -153,6 +155,7 @@ struct GLConfig bool reflectionMappingAvailable; bool reflectionMapping; bool bloom; + int MSAA; // 0 == disabled, otherwise used as sample count bool ssao; bool motionBlur; }; diff --git a/src/engine/renderer/Material.cpp b/src/engine/renderer/Material.cpp index c901c63852..eeccfb78c5 100644 --- a/src/engine/renderer/Material.cpp +++ b/src/engine/renderer/Material.cpp @@ -1062,6 +1062,8 @@ void BindShaderHeatHaze( Material* material ) { gl_heatHazeShaderMaterial->SetUniform_DeformEnable( true ); // draw to background image + TransitionMSAAToMain( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); + R_BindFBO( tr.mainFBO[1 - backEnd.currentMainFBO] ); } @@ -2228,6 +2230,8 @@ void MaterialSystem::RenderMaterial( Material& material, const uint32_t viewID ) R_BindFBO( tr.mainFBO[backEnd.currentMainFBO] ); RenderIndirect( material, viewID ); + + TransitionMainToMSAA( GL_COLOR_BUFFER_BIT ); } if ( r_showTris->integer diff --git a/src/engine/renderer/tr_backend.cpp b/src/engine/renderer/tr_backend.cpp index 43f74375a0..90a37cbd77 100644 --- a/src/engine/renderer/tr_backend.cpp +++ b/src/engine/renderer/tr_backend.cpp @@ -199,6 +199,47 @@ GLuint64 GL_BindToTMU( int unit, image_t *image ) return 0; } +static void BlitFBOToMSAA( FBO_t* fbo, const GLbitfield mask ) { + R_BindFBO( GL_READ_FRAMEBUFFER, fbo ); + R_BindFBO( GL_DRAW_FRAMEBUFFER, tr.msaaFBO ); + glBlitFramebuffer( 0, 0, fbo->width, fbo->height, 0, 0, tr.msaaFBO->width, tr.msaaFBO->height, + mask, GL_NEAREST ); + + R_BindFBO( GL_DRAW_FRAMEBUFFER, fbo ); + glState.currentFBO = fbo; +} + +static void BlitMSAAToFBO( FBO_t* fbo, const GLbitfield mask ) { + R_BindFBO( GL_READ_FRAMEBUFFER, tr.msaaFBO ); + R_BindFBO( GL_DRAW_FRAMEBUFFER, fbo ); + glBlitFramebuffer( 0, 0, tr.msaaFBO->width, tr.msaaFBO->height, 0, 0, fbo->width, fbo->height, + mask, GL_NEAREST ); + + R_BindFBO( GL_READ_FRAMEBUFFER, fbo ); + glState.currentFBO = fbo; +} + +void TransitionMainToMSAA( const GLbitfield mask ) { + if ( glConfig.MSAA ) { + BlitFBOToMSAA( tr.mainFBO[backEnd.currentMainFBO], mask ); + R_BindFBO( tr.msaaFBO ); + } +} + +void TransitionMSAAToMain( const GLbitfield mask ) { + if ( glConfig.MSAA ) { + BlitMSAAToFBO( tr.mainFBO[backEnd.currentMainFBO], mask ); + } +} + +void BindMSAAOrMainFBO() { + if ( glConfig.MSAA ) { + R_BindFBO( tr.msaaFBO ); + } else { + R_BindFBO( tr.mainFBO[backEnd.currentMainFBO] ); + } +} + void GL_BlendFunc( GLenum sfactor, GLenum dfactor ) { if ( glState.blendSrc != ( signed ) sfactor || glState.blendDst != ( signed ) dfactor ) @@ -797,7 +838,13 @@ void GL_TexImage2D( GLenum target, GLint level, GLint internalFormat, GLsizei wi GLint finalFormat = GL_ToSRGB( internalFormat, isSRGB ); glTexImage2D( target, level, finalFormat, width, height, border, format, type, data ); +} +void GL_TexImage2DMultisample( GLenum target, GLsizei samples, GLint internalFormat, GLsizei width, GLsizei height, bool fixedSampleLocations, bool isSRGB ) +{ + GLint finalFormat = GL_ToSRGB( internalFormat, isSRGB ); + + glTexImage2DMultisample( target, samples, finalFormat, width, height, fixedSampleLocations ); } void GL_TexImage3D( GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void *data, bool isSRGB ) @@ -1256,6 +1303,8 @@ static void RenderDepthTiles() { RB_PrepareForSamplingDepthMap(); } + + TransitionMSAAToMain( GL_DEPTH_BUFFER_BIT ); // 1st step R_BindFBO( tr.depthtile1FBO ); @@ -1365,7 +1414,8 @@ void RB_RenderPostDepthLightTile() Tess_Clear(); // back to main image - R_BindFBO( tr.mainFBO[ backEnd.currentMainFBO ] ); + BindMSAAOrMainFBO(); + GL_Viewport( backEnd.viewParms.viewportX, backEnd.viewParms.viewportY, backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight ); GL_Scissor( backEnd.viewParms.scissorX, backEnd.viewParms.scissorY, @@ -1446,6 +1496,8 @@ void RB_RenderBloom() GL_BindToTMU( 0, tr.currentRenderImage[backEnd.currentMainFBO] ) ); + TransitionMSAAToMain( GL_COLOR_BUFFER_BIT ); + R_BindFBO( tr.contrastRenderFBO ); GL_ClearColor( 0.0f, 0.0f, 0.0f, 1.0f ); glClear( GL_COLOR_BUFFER_BIT ); @@ -1510,6 +1562,8 @@ void RB_RenderBloom() GL_PopMatrix(); } + TransitionMainToMSAA( GL_COLOR_BUFFER_BIT ); + GL_CheckErrors(); } @@ -1530,6 +1584,8 @@ void RB_RenderMotionBlur() gl_motionblurShader->BindProgram(); + TransitionMSAAToMain( GL_COLOR_BUFFER_BIT ); + // Swap main FBOs gl_motionblurShader->SetUniform_ColorMapBindless( GL_BindToTMU( 0, tr.currentRenderImage[backEnd.currentMainFBO] ) @@ -1545,6 +1601,8 @@ void RB_RenderMotionBlur() Tess_InstantScreenSpaceQuad(); + TransitionMainToMSAA( GL_COLOR_BUFFER_BIT ); + GL_CheckErrors(); } @@ -1565,6 +1623,8 @@ void RB_RenderSSAO() RB_PrepareForSamplingDepthMap(); + TransitionMSAAToMain( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); + GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO ); GL_Cull( cullType_t::CT_TWO_SIDED ); @@ -1596,6 +1656,8 @@ void RB_RenderSSAO() Tess_InstantScreenSpaceQuad(); + TransitionMainToMSAA( GL_COLOR_BUFFER_BIT ); + GL_CheckErrors(); } @@ -2666,7 +2728,7 @@ static void RB_RenderView( bool depthPass ) backEnd.pc.c_surfaces += backEnd.viewParms.numDrawSurfs; // disable offscreen rendering - R_BindFBO( tr.mainFBO[ backEnd.currentMainFBO ] ); + BindMSAAOrMainFBO(); // we will need to change the projection matrix before drawing // 2D images again @@ -2794,6 +2856,8 @@ static void RB_RenderPostProcess() materialSystem.EndFrame(); } + TransitionMSAAToMain( GL_COLOR_BUFFER_BIT ); + RB_FXAA(); // render chromatic aberration @@ -3428,7 +3492,7 @@ const RenderCommand *ClearBufferCommand::ExecuteSelf( ) const } // disable offscreen rendering - R_BindFBO( tr.mainFBO[ backEnd.currentMainFBO ] ); + R_BindFBO( tr.mainFBO[backEnd.currentMainFBO] ); // we will need to change the projection matrix before drawing // 2D images again @@ -3449,6 +3513,11 @@ const RenderCommand *ClearBufferCommand::ExecuteSelf( ) const glClear( clearBits ); + if ( glConfig.MSAA ) { + R_BindFBO( tr.msaaFBO ); + glClear( clearBits ); + } + return this + 1; } diff --git a/src/engine/renderer/tr_fbo.cpp b/src/engine/renderer/tr_fbo.cpp index 642c645aa8..97c965f4a2 100644 --- a/src/engine/renderer/tr_fbo.cpp +++ b/src/engine/renderer/tr_fbo.cpp @@ -152,7 +152,8 @@ R_AttachFBOTexture2D */ void R_AttachFBOTexture2D( int target, int texId, int index ) { - if ( target != GL_TEXTURE_2D && ( target < GL_TEXTURE_CUBE_MAP_POSITIVE_X || target > GL_TEXTURE_CUBE_MAP_NEGATIVE_Z ) ) + if ( target != GL_TEXTURE_2D && target != GL_TEXTURE_2D_MULTISAMPLE + && ( target < GL_TEXTURE_CUBE_MAP_POSITIVE_X || target > GL_TEXTURE_CUBE_MAP_NEGATIVE_Z ) ) { Log::Warn("R_AttachFBOTexture2D: invalid target %i", target ); return; @@ -194,6 +195,11 @@ void R_AttachFBOTexturePackedDepthStencil( int texId ) GL_fboShim.glFramebufferTexture2D( GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, texId, 0 ); } +void R_AttachFBOTexturePackedDepthStencilMSAA( int texId ) { + GL_fboShim.glFramebufferTexture2D( GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D_MULTISAMPLE, texId, 0 ); + GL_fboShim.glFramebufferTexture2D( GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D_MULTISAMPLE, texId, 0 ); +} + /* ============ R_BindFBO @@ -271,6 +277,14 @@ void R_InitFBOs() glConfig.usingReadonlyDepth = R_CheckFBO( tr.readonlyDepthFBO ); } + if ( glConfig.MSAA ) { + tr.msaaFBO = R_CreateFBO( "msaa", width, height ); + R_BindFBO( tr.msaaFBO ); + R_AttachFBOTexture2D( GL_TEXTURE_2D_MULTISAMPLE, tr.currentRenderImageMSAA->texnum, 0 ); + R_AttachFBOTexturePackedDepthStencilMSAA( tr.currentDepthImageMSAA->texnum ); + R_CheckFBO( tr.msaaFBO ); + } + if ( glConfig.realtimeLighting ) { /* It's only required to create frame buffers only used by the diff --git a/src/engine/renderer/tr_image.cpp b/src/engine/renderer/tr_image.cpp index b430bedcd4..3d87ecf326 100644 --- a/src/engine/renderer/tr_image.cpp +++ b/src/engine/renderer/tr_image.cpp @@ -841,7 +841,8 @@ level 1 has only numLayers/2 layers. There are still numLayers pointers in the dataArray for every mip level, the unneeded elements at the end aren't used. =============== */ -void R_UploadImage( const char *name, const byte **dataArray, int numLayers, int numMips, image_t *image, const imageParams_t &imageParams ) +void R_UploadImage( const char *name, const byte **dataArray, int numLayers, int numMips, image_t *image, const imageParams_t &imageParams, + const uint32_t samples, const bool fixedSampleLocations ) { const byte *data; byte *scaledBuffer = nullptr; @@ -911,6 +912,14 @@ void R_UploadImage( const char *name, const byte **dataArray, int numLayers, int break; } + if ( samples ) { + if ( target == GL_TEXTURE_2D ) { + target = GL_TEXTURE_2D_MULTISAMPLE; + } else { + Log::Warn( "Multisampling is currently only supported for 2D textures (image: %s)", name ); + } + } + if ( image->bits & ( IF_DEPTH16 | IF_DEPTH24 | IF_DEPTH32 ) ) { format = GL_DEPTH_COMPONENT; @@ -1150,12 +1159,21 @@ void R_UploadImage( const char *name, const byte **dataArray, int numLayers, int default: if ( image->bits & IF_PACKED_DEPTH24_STENCIL8 ) { - GL_TexImage2D( target, 0, internalFormat, scaledWidth, scaledHeight, 0, format, GL_UNSIGNED_INT_24_8, nullptr, isSRGB ); + if ( samples ) { + GL_TexImage2DMultisample( target, samples, internalFormat, scaledWidth, scaledHeight, fixedSampleLocations, isSRGB ); + } else { + GL_TexImage2D( target, 0, internalFormat, scaledWidth, scaledHeight, 0, format, GL_UNSIGNED_INT_24_8, nullptr, isSRGB ); + } } else { - GL_TexImage2D( target, 0, internalFormat, scaledWidth, scaledHeight, 0, format, GL_UNSIGNED_BYTE, scaledBuffer, isSRGB ); + if ( samples ) { + GL_TexImage2DMultisample( target, samples, internalFormat, scaledWidth, scaledHeight, fixedSampleLocations, isSRGB ); + } else { + GL_TexImage2D( target, 0, internalFormat, scaledWidth, scaledHeight, 0, format, GL_UNSIGNED_BYTE, scaledBuffer, isSRGB ); + } } + GL_CheckErrors(); break; } @@ -1216,16 +1234,34 @@ void R_UploadImage( const char *name, const byte **dataArray, int numLayers, int GL_CheckErrors(); + switch ( image->internalFormat ) { + case GL_RGBA: + case GL_RGBA8: + case GL_RGBA16: + case GL_RGBA16F: + case GL_RGBA32F: + case GL_RGBA32UI: + case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + image->bits |= IF_ALPHA; + } + + if ( samples ) { + ASSERT_EQ( scaledBuffer, nullptr ); + + GL_Unbind( image ); + return; + } + // set filter type - switch ( image->filterType ) - { + switch ( image->filterType ) { case filterType_t::FT_DEFAULT: - // set texture anisotropy - if ( glConfig.textureAnisotropyAvailable ) - { - glTexParameterf( image->type, GL_TEXTURE_MAX_ANISOTROPY_EXT, glConfig.textureAnisotropy ); - } + // set texture anisotropy + if ( glConfig.textureAnisotropyAvailable ) + { + glTexParameterf( image->type, GL_TEXTURE_MAX_ANISOTROPY_EXT, glConfig.textureAnisotropy ); + } glTexParameterf( image->type, GL_TEXTURE_MIN_FILTER, gl_filter_min ); glTexParameterf( image->type, GL_TEXTURE_MAG_FILTER, gl_filter_max ); @@ -1242,7 +1278,7 @@ void R_UploadImage( const char *name, const byte **dataArray, int numLayers, int break; default: - Log::Warn("unknown filter type for image '%s'", image->name ); + Log::Warn( "unknown filter type for image '%s'", image->name ); glTexParameterf( image->type, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); glTexParameterf( image->type, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); break; @@ -1251,115 +1287,110 @@ void R_UploadImage( const char *name, const byte **dataArray, int numLayers, int GL_CheckErrors(); // set wrap type - if ( image->wrapType.s == image->wrapType.t ) - { - switch ( image->wrapType.s ) - { - case wrapTypeEnum_t::WT_REPEAT: - glTexParameterf( image->type, GL_TEXTURE_WRAP_S, GL_REPEAT ); - glTexParameterf( image->type, GL_TEXTURE_WRAP_T, GL_REPEAT ); - break; - - case wrapTypeEnum_t::WT_CLAMP: - case wrapTypeEnum_t::WT_EDGE_CLAMP: - glTexParameterf( image->type, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); - glTexParameterf( image->type, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); - break; - case wrapTypeEnum_t::WT_ONE_CLAMP: - glTexParameterf( image->type, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER ); - glTexParameterf( image->type, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER ); - glTexParameterfv( image->type, GL_TEXTURE_BORDER_COLOR, oneClampBorder ); - break; - case wrapTypeEnum_t::WT_ZERO_CLAMP: - glTexParameterf( image->type, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER ); - glTexParameterf( image->type, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER ); - glTexParameterfv( image->type, GL_TEXTURE_BORDER_COLOR, zeroClampBorder ); - break; - - case wrapTypeEnum_t::WT_ALPHA_ZERO_CLAMP: - glTexParameterf( image->type, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER ); - glTexParameterf( image->type, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER ); - glTexParameterfv( image->type, GL_TEXTURE_BORDER_COLOR, alphaZeroClampBorder ); - break; - - default: - Log::Warn("unknown wrap type for image '%s'", image->name ); - glTexParameterf( image->type, GL_TEXTURE_WRAP_S, GL_REPEAT ); - glTexParameterf( image->type, GL_TEXTURE_WRAP_T, GL_REPEAT ); - break; - } - } else { - // warn about mismatched clamp types if both require a border colour to be set - if ( ( image->wrapType.s == wrapTypeEnum_t::WT_ZERO_CLAMP || image->wrapType.s == wrapTypeEnum_t::WT_ONE_CLAMP || image->wrapType.s == wrapTypeEnum_t::WT_ALPHA_ZERO_CLAMP ) && - ( image->wrapType.t == wrapTypeEnum_t::WT_ZERO_CLAMP || image->wrapType.t == wrapTypeEnum_t::WT_ONE_CLAMP || image->wrapType.t == wrapTypeEnum_t::WT_ALPHA_ZERO_CLAMP ) ) - { - Log::Warn("mismatched wrap types for image '%s'", image->name ); - } - - switch ( image->wrapType.s ) - { - case wrapTypeEnum_t::WT_REPEAT: - glTexParameterf( image->type, GL_TEXTURE_WRAP_S, GL_REPEAT ); - break; - - case wrapTypeEnum_t::WT_CLAMP: - case wrapTypeEnum_t::WT_EDGE_CLAMP: - glTexParameterf( image->type, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); - break; - - case wrapTypeEnum_t::WT_ONE_CLAMP: - glTexParameterf( image->type, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER ); - glTexParameterfv( image->type, GL_TEXTURE_BORDER_COLOR, oneClampBorder ); - break; - - case wrapTypeEnum_t::WT_ZERO_CLAMP: - glTexParameterf( image->type, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER ); - glTexParameterfv( image->type, GL_TEXTURE_BORDER_COLOR, zeroClampBorder ); - break; - - case wrapTypeEnum_t::WT_ALPHA_ZERO_CLAMP: - glTexParameterf( image->type, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER ); - glTexParameterfv( image->type, GL_TEXTURE_BORDER_COLOR, alphaZeroClampBorder ); - break; - - default: - Log::Warn("unknown wrap type for image '%s' axis S", image->name ); - glTexParameterf( image->type, GL_TEXTURE_WRAP_S, GL_REPEAT ); - break; - } - - switch ( image->wrapType.t ) - { - case wrapTypeEnum_t::WT_REPEAT: - glTexParameterf( image->type, GL_TEXTURE_WRAP_T, GL_REPEAT ); - break; - - case wrapTypeEnum_t::WT_CLAMP: - case wrapTypeEnum_t::WT_EDGE_CLAMP: - glTexParameterf( image->type, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); - break; - - case wrapTypeEnum_t::WT_ONE_CLAMP: - glTexParameterf( image->type, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER ); - glTexParameterfv( image->type, GL_TEXTURE_BORDER_COLOR, oneClampBorder ); - break; - - case wrapTypeEnum_t::WT_ZERO_CLAMP: - glTexParameterf( image->type, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER ); - glTexParameterfv( image->type, GL_TEXTURE_BORDER_COLOR, zeroClampBorder ); - break; - - case wrapTypeEnum_t::WT_ALPHA_ZERO_CLAMP: - glTexParameterf( image->type, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER ); - glTexParameterfv( image->type, GL_TEXTURE_BORDER_COLOR, alphaZeroClampBorder ); - break; - - default: - Log::Warn("unknown wrap type for image '%s' axis T", image->name ); - glTexParameterf( image->type, GL_TEXTURE_WRAP_T, GL_REPEAT ); - break; - } - } + if ( image->wrapType.s == image->wrapType.t ) { + switch ( image->wrapType.s ) { + case wrapTypeEnum_t::WT_REPEAT: + glTexParameterf( image->type, GL_TEXTURE_WRAP_S, GL_REPEAT ); + glTexParameterf( image->type, GL_TEXTURE_WRAP_T, GL_REPEAT ); + break; + + case wrapTypeEnum_t::WT_CLAMP: + case wrapTypeEnum_t::WT_EDGE_CLAMP: + glTexParameterf( image->type, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); + glTexParameterf( image->type, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); + break; + case wrapTypeEnum_t::WT_ONE_CLAMP: + glTexParameterf( image->type, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER ); + glTexParameterf( image->type, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER ); + glTexParameterfv( image->type, GL_TEXTURE_BORDER_COLOR, oneClampBorder ); + break; + case wrapTypeEnum_t::WT_ZERO_CLAMP: + glTexParameterf( image->type, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER ); + glTexParameterf( image->type, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER ); + glTexParameterfv( image->type, GL_TEXTURE_BORDER_COLOR, zeroClampBorder ); + break; + + case wrapTypeEnum_t::WT_ALPHA_ZERO_CLAMP: + glTexParameterf( image->type, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER ); + glTexParameterf( image->type, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER ); + glTexParameterfv( image->type, GL_TEXTURE_BORDER_COLOR, alphaZeroClampBorder ); + break; + + default: + Log::Warn( "unknown wrap type for image '%s'", image->name ); + glTexParameterf( image->type, GL_TEXTURE_WRAP_S, GL_REPEAT ); + glTexParameterf( image->type, GL_TEXTURE_WRAP_T, GL_REPEAT ); + break; + } + } else { + // warn about mismatched clamp types if both require a border colour to be set + if ( ( image->wrapType.s == wrapTypeEnum_t::WT_ZERO_CLAMP || image->wrapType.s == wrapTypeEnum_t::WT_ONE_CLAMP || image->wrapType.s == wrapTypeEnum_t::WT_ALPHA_ZERO_CLAMP ) && + ( image->wrapType.t == wrapTypeEnum_t::WT_ZERO_CLAMP || image->wrapType.t == wrapTypeEnum_t::WT_ONE_CLAMP || image->wrapType.t == wrapTypeEnum_t::WT_ALPHA_ZERO_CLAMP ) ) { + Log::Warn( "mismatched wrap types for image '%s'", image->name ); + } + + switch ( image->wrapType.s ) { + case wrapTypeEnum_t::WT_REPEAT: + glTexParameterf( image->type, GL_TEXTURE_WRAP_S, GL_REPEAT ); + break; + + case wrapTypeEnum_t::WT_CLAMP: + case wrapTypeEnum_t::WT_EDGE_CLAMP: + glTexParameterf( image->type, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); + break; + + case wrapTypeEnum_t::WT_ONE_CLAMP: + glTexParameterf( image->type, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER ); + glTexParameterfv( image->type, GL_TEXTURE_BORDER_COLOR, oneClampBorder ); + break; + + case wrapTypeEnum_t::WT_ZERO_CLAMP: + glTexParameterf( image->type, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER ); + glTexParameterfv( image->type, GL_TEXTURE_BORDER_COLOR, zeroClampBorder ); + break; + + case wrapTypeEnum_t::WT_ALPHA_ZERO_CLAMP: + glTexParameterf( image->type, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER ); + glTexParameterfv( image->type, GL_TEXTURE_BORDER_COLOR, alphaZeroClampBorder ); + break; + + default: + Log::Warn( "unknown wrap type for image '%s' axis S", image->name ); + glTexParameterf( image->type, GL_TEXTURE_WRAP_S, GL_REPEAT ); + break; + } + + switch ( image->wrapType.t ) { + case wrapTypeEnum_t::WT_REPEAT: + glTexParameterf( image->type, GL_TEXTURE_WRAP_T, GL_REPEAT ); + break; + + case wrapTypeEnum_t::WT_CLAMP: + case wrapTypeEnum_t::WT_EDGE_CLAMP: + glTexParameterf( image->type, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); + break; + + case wrapTypeEnum_t::WT_ONE_CLAMP: + glTexParameterf( image->type, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER ); + glTexParameterfv( image->type, GL_TEXTURE_BORDER_COLOR, oneClampBorder ); + break; + + case wrapTypeEnum_t::WT_ZERO_CLAMP: + glTexParameterf( image->type, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER ); + glTexParameterfv( image->type, GL_TEXTURE_BORDER_COLOR, zeroClampBorder ); + break; + + case wrapTypeEnum_t::WT_ALPHA_ZERO_CLAMP: + glTexParameterf( image->type, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER ); + glTexParameterfv( image->type, GL_TEXTURE_BORDER_COLOR, alphaZeroClampBorder ); + break; + + default: + Log::Warn( "unknown wrap type for image '%s' axis T", image->name ); + glTexParameterf( image->type, GL_TEXTURE_WRAP_T, GL_REPEAT ); + break; + } + } GL_CheckErrors(); @@ -1368,19 +1399,6 @@ void R_UploadImage( const char *name, const byte **dataArray, int numLayers, int ri.Hunk_FreeTempMemory( scaledBuffer ); } - switch ( image->internalFormat ) - { - case GL_RGBA: - case GL_RGBA8: - case GL_RGBA16: - case GL_RGBA16F: - case GL_RGBA32F: - case GL_RGBA32UI: - case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: - case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: - image->bits |= IF_ALPHA; - } - GL_Unbind( image ); } @@ -1448,7 +1466,8 @@ static void R_ExportTexture( image_t *image ) R_CreateImage ================ */ -image_t *R_CreateImage( const char *name, const byte **pic, int width, int height, int numMips, const imageParams_t &imageParams ) +image_t *R_CreateImage( const char *name, const byte **pic, int width, int height, int numMips, const imageParams_t &imageParams, + const uint32_t samples, const bool fixedSampleLocations ) { const char* colorspaces[] = { "linear", "sRGB" }; const char* colorspace = colorspaces[ bool( imageParams.bits & IF_SRGB ) ]; @@ -1464,8 +1483,13 @@ image_t *R_CreateImage( const char *name, const byte **pic, int width, int heigh return nullptr; } - image->type = GL_TEXTURE_2D; - image->texture->target = GL_TEXTURE_2D; + if ( samples ) { + image->type = GL_TEXTURE_2D_MULTISAMPLE; + image->texture->target = GL_TEXTURE_2D_MULTISAMPLE; + } else { + image->type = GL_TEXTURE_2D; + image->texture->target = GL_TEXTURE_2D; + } image->width = width; image->height = height; @@ -1474,7 +1498,7 @@ image_t *R_CreateImage( const char *name, const byte **pic, int width, int heigh image->filterType = imageParams.filterType; image->wrapType = imageParams.wrapType; - R_UploadImage( name, pic, 1, numMips, image, imageParams ); + R_UploadImage( name, pic, 1, numMips, image, imageParams, samples, fixedSampleLocations ); if( r_exportTextures->integer ) { R_ExportTexture( image ); @@ -2466,6 +2490,10 @@ static void R_CreateCurrentRenderImage() tr.currentRenderImage[0] = R_CreateImage( "*currentRender0", nullptr, width, height, 1, imageParams ); tr.currentRenderImage[1] = R_CreateImage( "*currentRender1", nullptr, width, height, 1, imageParams ); + if ( glConfig.MSAA ) { + tr.currentRenderImageMSAA = R_CreateImage( "_currentRenderMSAA", nullptr, width, height, 1, imageParams, glConfig.MSAA, false ); + } + imageParams = {}; imageParams.bits = IF_NOPICMIP | IF_PACKED_DEPTH24_STENCIL8; imageParams.filterType = filterType_t::FT_NEAREST; @@ -2479,6 +2507,10 @@ static void R_CreateCurrentRenderImage() tr.depthSamplerImage = R_CreateImage( "*readonlyDepth", nullptr, width, height, 1, imageParams ); } + if( glConfig.MSAA ) { + tr.currentDepthImageMSAA = R_CreateImage( "_currentDepthMSAA", nullptr, width, height, 1, imageParams, glConfig.MSAA, false ); + } + if ( glConfig.usingMaterialSystem ) { materialSystem.GenerateDepthImages( width, height, imageParams ); } diff --git a/src/engine/renderer/tr_init.cpp b/src/engine/renderer/tr_init.cpp index e2db9fe8f9..07c9a931e4 100644 --- a/src/engine/renderer/tr_init.cpp +++ b/src/engine/renderer/tr_init.cpp @@ -287,6 +287,7 @@ Cvar::Cvar r_rendererAPI( "r_rendererAPI", "Renderer API: 0: OpenGL, 1: Vul Cvar::Cvar r_bloomBlur( "r_bloomBlur", "Bloom strength", Cvar::NONE, 1.0 ); Cvar::Cvar r_bloomPasses( "r_bloomPasses", "Amount of bloom passes in each direction", Cvar::NONE, 2 ); cvar_t *r_FXAA; + Cvar::Range> r_msaa( "r_msaa", "Amount of MSAA samples. 0 to disable", Cvar::NONE, 0, 0, 64 ); Cvar::Range> r_ssao( "r_ssao", "Screen space ambient occlusion: " "-1: show, 0: disabled, 1: enabled", @@ -1192,6 +1193,7 @@ ScreenshotCmd screenshotPNGRegistration("screenshotPNG", ssFormat_t::SSF_PNG, "p Cvar::Latch( r_bloom ); r_FXAA = Cvar_Get( "r_FXAA", "0", CVAR_LATCH | CVAR_ARCHIVE ); Cvar::Latch( r_ssao ); + Cvar::Latch( r_msaa ); // temporary variables that can change at any time r_showImages = Cvar_Get( "r_showImages", "0", CVAR_TEMP ); diff --git a/src/engine/renderer/tr_local.h b/src/engine/renderer/tr_local.h index a6cfc3d47e..bc54905438 100644 --- a/src/engine/renderer/tr_local.h +++ b/src/engine/renderer/tr_local.h @@ -2466,6 +2466,8 @@ enum image_t *currentRenderImage[ 2 ]; image_t *currentDepthImage; image_t *depthSamplerImage; + image_t *currentRenderImageMSAA; + image_t *currentDepthImageMSAA; image_t *depthtile1RenderImage; image_t *depthtile2RenderImage; image_t *lighttileRenderImage; @@ -2478,6 +2480,7 @@ enum // framebuffer objects FBO_t *mainFBO[ 2 ]; FBO_t *readonlyDepthFBO; + FBO_t *msaaFBO; FBO_t *depthtile1FBO; FBO_t *depthtile2FBO; FBO_t *lighttileFBO; @@ -2783,6 +2786,7 @@ enum extern Cvar::Cvar r_bloomPasses; extern cvar_t *r_FXAA; extern Cvar::Range> r_ssao; + extern Cvar::Range> r_msaa; extern cvar_t *r_evsmPostProcess; @@ -2912,6 +2916,11 @@ inline bool checkGLErrors() void GL_BindProgram( ShaderProgramDescriptor* program ); GLuint64 GL_BindToTMU( int unit, image_t *image ); void GL_BindNullProgram(); + + void TransitionMainToMSAA( const GLbitfield mask ); + void TransitionMSAAToMain( const GLbitfield mask ); + void BindMSAAOrMainFBO(); + void GL_SetDefaultState(); void GL_SelectTexture( int unit ); void GL_TextureMode( const char *string ); @@ -2938,6 +2947,7 @@ inline bool checkGLErrors() void GL_VertexAttribsState( uint32_t stateBits, const bool settingUpVAO = false ); void GL_Cull( cullType_t cullType ); void GL_TexImage2D( GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *data, bool isSRGB ); +void GL_TexImage2DMultisample( GLenum target, GLsizei samples, GLint internalFormat, GLsizei width, GLsizei height, bool fixedSampleLocations, bool isSRGB ); void GL_TexImage3D( GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void *data, bool isSRGB ); void GL_CompressedTexImage2D( GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *data, bool isSRGB ); void GL_CompressedTexSubImage3D( GLenum target, GLint level, GLint xOffset, GLint yOffset, GLint zOffset, GLsizei width, GLsizei height, GLsizei depth, GLenum internalFormat, GLsizei size, const void *data, bool isSRGB ); @@ -2990,7 +3000,8 @@ void GL_CompressedTexSubImage3D( GLenum target, GLint level, GLint xOffset, GLin image_t *R_FindImageFile( const char *name, imageParams_t &imageParams ); image_t *R_FindCubeImage( const char *name, imageParams_t &imageParams ); - image_t *R_CreateImage( const char *name, const byte **pic, int width, int height, int numMips, const imageParams_t &imageParams ); + image_t *R_CreateImage( const char *name, const byte **pic, int width, int height, int numMips, const imageParams_t &imageParams, + const uint32_t samples = 0, const bool fixedSampleLocations = true ); image_t *R_CreateCubeImage( const char *name, const byte *pic[ 6 ], int width, int height, const imageParams_t &imageParams ); image_t *R_Create3DImage( const char *name, const byte *pic, int width, int height, int depth, const imageParams_t &imageParams ); @@ -2999,7 +3010,8 @@ void GL_CompressedTexSubImage3D( GLenum target, GLint level, GLint xOffset, GLin qhandle_t RE_GenerateTexture( const byte *pic, int width, int height ); image_t *R_AllocImage( const char *name, bool linkIntoHashTable ); - void R_UploadImage( const char *name, const byte **dataArray, int numLayers, int numMips, image_t *image, const imageParams_t &imageParams ); + void R_UploadImage( const char *name, const byte **dataArray, int numLayers, int numMips, image_t *image, const imageParams_t &imageParams, + const uint32_t samples = 0, const bool fixedSampleLocations = true ); void RE_GetTextureSize( int textureID, int *width, int *height ); diff --git a/src/engine/renderer/tr_shade.cpp b/src/engine/renderer/tr_shade.cpp index 19c481d928..60b4768949 100644 --- a/src/engine/renderer/tr_shade.cpp +++ b/src/engine/renderer/tr_shade.cpp @@ -180,6 +180,13 @@ static void EnableAvailableFeatures() } } + glConfig.MSAA = r_msaa.Get(); + const int maxSamples = std::min( glConfig.maxColorTextureSamples, glConfig.maxDepthTextureSamples ); + if ( glConfig.MSAA > maxSamples ) { + Log::Warn( "MSAA samples %i > %i, setting to %i", r_msaa.Get(), maxSamples, maxSamples ); + glConfig.MSAA = maxSamples; + } + glConfig.usingMaterialSystem = r_materialSystem.Get() && glConfig.materialSystemAvailable; glConfig.usingBindlessTextures = glConfig.usingMaterialSystem || ( r_preferBindlessTextures.Get() && glConfig.bindlessTexturesAvailable ); @@ -880,11 +887,21 @@ void Render_generic3D( shaderStage_t *pStage ) bool hasDepthFade = pStage->hasDepthFade; bool needDepthMap = pStage->hasDepthFade; - if ( needDepthMap ) + const bool needMSAATransion = needDepthMap && backEnd.dirtyDepthBuffer; + + if ( needDepthMap && backEnd.dirtyDepthBuffer && glConfig.textureBarrierAvailable ) { RB_PrepareForSamplingDepthMap(); } + if ( needMSAATransion ) { + TransitionMSAAToMain( GL_DEPTH_BUFFER_BIT ); + + if ( glConfig.MSAA ) { + R_BindFBO( GL_DRAW_FRAMEBUFFER, tr.msaaFBO ); + } + } + // choose right shader program ---------------------------------- ProcessShaderGeneric3D( pStage ); gl_genericShader->BindProgram(); @@ -1454,6 +1471,7 @@ void Render_heatHaze( shaderStage_t *pStage ) } // draw to background image + TransitionMSAAToMain( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); R_BindFBO( tr.mainFBO[ 1 - backEnd.currentMainFBO ] ); // bind u_NormalMap @@ -1489,6 +1507,8 @@ void Render_heatHaze( shaderStage_t *pStage ) gl_heatHazeShader->SetUniform_DeformMagnitude( 0.0f ); Tess_DrawElements(); + TransitionMainToMSAA( GL_COLOR_BUFFER_BIT ); + GL_CheckErrors(); } diff --git a/src/engine/sys/sdl_glimp.cpp b/src/engine/sys/sdl_glimp.cpp index 1580598a94..4c83fe5f0c 100644 --- a/src/engine/sys/sdl_glimp.cpp +++ b/src/engine/sys/sdl_glimp.cpp @@ -2075,6 +2075,8 @@ static void GLimp_InitExtensions() glGetIntegerv( GL_MAX_TEXTURE_SIZE, &glConfig.maxTextureSize ); glGetIntegerv( GL_MAX_3D_TEXTURE_SIZE, &glConfig.max3DTextureSize ); glGetIntegerv( GL_MAX_CUBE_MAP_TEXTURE_SIZE_ARB, &glConfig.maxCubeMapTextureSize ); + glGetIntegerv( GL_MAX_COLOR_TEXTURE_SAMPLES, &glConfig.maxColorTextureSamples ); + glGetIntegerv( GL_MAX_DEPTH_TEXTURE_SAMPLES, &glConfig.maxDepthTextureSamples ); // Stubbed or broken drivers may report garbage.