From 94ddf1a467632e64e88d4d9fc0611044e07707f4 Mon Sep 17 00:00:00 2001 From: rogerman Date: Mon, 22 Oct 2018 10:32:16 -0700 Subject: [PATCH] OpenGL Renderer: The MSAA sample size is no longer automatically selected and must now be manually selected via GFX3D_Renderer_MultisampleSize. - All frontends will need to be updated to use the new GFX3D_Renderer_MultisampleSize setting. - This change obsoletes GFX3D_Renderer_Multisample, which currently does nothing at the moment. It will be removed after all frontends are updated. --- desmume/src/NDSSystem.h | 2 + desmume/src/OGLRender.cpp | 126 ++++++++++++++++++++++++---------- desmume/src/OGLRender.h | 42 ++++-------- desmume/src/OGLRender_3_2.cpp | 92 +++++++++++++++++-------- desmume/src/OGLRender_3_2.h | 1 + 5 files changed, 170 insertions(+), 93 deletions(-) diff --git a/desmume/src/NDSSystem.h b/desmume/src/NDSSystem.h index 452794f17..5ced00d72 100755 --- a/desmume/src/NDSSystem.h +++ b/desmume/src/NDSSystem.h @@ -532,6 +532,7 @@ extern struct TCommonSettings , GFX3D_Texture(true) , GFX3D_LineHack(true) , GFX3D_Renderer_Multisample(false) + , GFX3D_Renderer_MultisampleSize(0) , GFX3D_Renderer_TextureScalingFactor(1) // Possible values: 1, 2, 4 , GFX3D_Renderer_TextureDeposterize(false) , GFX3D_Renderer_TextureSmoothing(false) @@ -591,6 +592,7 @@ extern struct TCommonSettings bool GFX3D_Texture; bool GFX3D_LineHack; bool GFX3D_Renderer_Multisample; + int GFX3D_Renderer_MultisampleSize; int GFX3D_Renderer_TextureScalingFactor; //must be one of {1,2,4} bool GFX3D_Renderer_TextureDeposterize; bool GFX3D_Renderer_TextureSmoothing; diff --git a/desmume/src/OGLRender.cpp b/desmume/src/OGLRender.cpp index 76c4e6d66..d7da0061f 100755 --- a/desmume/src/OGLRender.cpp +++ b/desmume/src/OGLRender.cpp @@ -1474,35 +1474,39 @@ FragmentColor* OpenGLRenderer::GetFramebuffer() return (this->willFlipAndConvertFramebufferOnGPU && this->isPBOSupported) ? this->_mappedFramebuffer : GPU->GetEngineMain()->Get3DFramebufferMain(); } - GLsizei OpenGLRenderer::GetLimitedMultisampleSize() const { GLsizei deviceMultisamples = this->_deviceInfo.maxSamples; - GLsizei maxMultisamples = OGLMaxMultisamples_Tier1; + u32 workingMultisamples = (u32)this->_selectedMultisampleSize; - if ( (this->_framebufferWidth <= GPU_FRAMEBUFFER_NATIVE_WIDTH * OGLMaxMultisamplesScaleLimit_Tier1) && - (this->_framebufferHeight <= GPU_FRAMEBUFFER_NATIVE_HEIGHT * OGLMaxMultisamplesScaleLimit_Tier1) ) + if (workingMultisamples == 1) { - maxMultisamples = OGLMaxMultisamples_Tier1; - } - else if ( (this->_framebufferWidth <= GPU_FRAMEBUFFER_NATIVE_WIDTH * OGLMaxMultisamplesScaleLimit_Tier2) && - (this->_framebufferHeight <= GPU_FRAMEBUFFER_NATIVE_HEIGHT * OGLMaxMultisamplesScaleLimit_Tier2) ) - { - maxMultisamples = OGLMaxMultisamples_Tier2; - } - else if ( (this->_framebufferWidth <= GPU_FRAMEBUFFER_NATIVE_WIDTH * OGLMaxMultisamplesScaleLimit_Tier3) && - (this->_framebufferHeight <= GPU_FRAMEBUFFER_NATIVE_HEIGHT * OGLMaxMultisamplesScaleLimit_Tier3) ) - { - maxMultisamples = OGLMaxMultisamples_Tier3; + // If this->_selectedMultisampleSize is 1, then just set workingMultisamples to 2 + // by default. This is done to prevent the multisampled FBOs from being resized to + // a meaningless sample size of 1. + // + // As an side, if someone wants to bring back automatic MSAA sample size selection + // in the future, then this would be the place to reimplement it. + workingMultisamples = 2; } else { - maxMultisamples = OGLMaxMultisamples_Tier4; + // Ensure that workingMultisamples is a power-of-two, which is what OpenGL likes. + // + // If workingMultisamples is not a power-of-two, then workingMultisamples is + // increased to the next largest power-of-two. + workingMultisamples--; + workingMultisamples |= workingMultisamples >> 1; + workingMultisamples |= workingMultisamples >> 2; + workingMultisamples |= workingMultisamples >> 4; + workingMultisamples |= workingMultisamples >> 8; + workingMultisamples |= workingMultisamples >> 16; + workingMultisamples++; } - if (deviceMultisamples > maxMultisamples) + if (deviceMultisamples > workingMultisamples) { - deviceMultisamples = maxMultisamples; + deviceMultisamples = workingMultisamples; } return deviceMultisamples; @@ -1945,7 +1949,16 @@ Render3DError OpenGLRenderer::DrawOtherPolygon(const GLenum polyPrimitive, const Render3DError OpenGLRenderer::ApplyRenderingSettings(const GFX3D_State &renderState) { - this->_enableMultisampledRendering = (CommonSettings.GFX3D_Renderer_Multisample && this->isMultisampledFBOSupported); + int oldSelectedMultisampleSize = this->_selectedMultisampleSize; + + this->_selectedMultisampleSize = CommonSettings.GFX3D_Renderer_MultisampleSize; + this->_enableMultisampledRendering = ((this->_selectedMultisampleSize >= 2) && this->isMultisampledFBOSupported); + + if (this->_selectedMultisampleSize != oldSelectedMultisampleSize) + { + GLsizei sampleSize = this->GetLimitedMultisampleSize(); + this->ResizeMultisampledFBOs(sampleSize); + } return Render3D::ApplyRenderingSettings(renderState); } @@ -2169,6 +2182,8 @@ Render3DError OpenGLRenderer_1_2::InitExtensions() INFO("OpenGL: FBOs are unsupported. Some emulation features will be disabled.\n"); } + this->_selectedMultisampleSize = CommonSettings.GFX3D_Renderer_MultisampleSize; + // Don't use ARB versions since we're using the EXT versions for backwards compatibility. this->isMultisampledFBOSupported = this->isFBOSupported && this->IsExtensionPresent(&oglExtensionSet, "GL_EXT_framebuffer_multisample"); @@ -2178,15 +2193,31 @@ Render3DError OpenGLRenderer_1_2::InitExtensions() glGetIntegerv(GL_MAX_SAMPLES_EXT, &maxSamplesOGL); this->_deviceInfo.maxSamples = (u8)maxSamplesOGL; - if (maxSamplesOGL >= 2) + if (this->_deviceInfo.maxSamples >= 2) { + // Try and initialize the multisampled FBOs with the GFX3D_Renderer_MultisampleSize. + // However, if the client has this set to 0, then set sampleSize to 2 in order to + // force the generation and the attachments of the buffers at a meaningful sample + // size. If GFX3D_Renderer_MultisampleSize is 0, then we can deallocate the buffer + // memory afterwards. GLsizei sampleSize = this->GetLimitedMultisampleSize(); + if (sampleSize == 0) + { + sampleSize = 2; + } error = this->CreateMultisampledFBO(sampleSize); if (error != OGLERROR_NOERR) { this->isMultisampledFBOSupported = false; } + + // If GFX3D_Renderer_MultisampleSize is 0, then we can deallocate the buffers now + // in order to save some memory. + if (this->_selectedMultisampleSize == 0) + { + this->ResizeMultisampledFBOs(0); + } } else { @@ -2207,7 +2238,7 @@ Render3DError OpenGLRenderer_1_2::InitExtensions() this->_deviceInfo.isFogSupported = (this->isShaderSupported && this->isVBOSupported && this->isFBOSupported); this->_deviceInfo.isTextureSmoothingSupported = this->isShaderSupported; - this->_enableMultisampledRendering = (CommonSettings.GFX3D_Renderer_Multisample && this->isMultisampledFBOSupported); + this->_enableMultisampledRendering = ((this->_selectedMultisampleSize >= 2) && this->isMultisampledFBOSupported); this->InitFinalRenderStates(&oglExtensionSet); // This must be done last @@ -2956,6 +2987,38 @@ void OpenGLRenderer_1_2::DestroyMultisampledFBO() this->isMultisampledFBOSupported = false; } +void OpenGLRenderer_1_2::ResizeMultisampledFBOs(GLsizei numSamples) +{ + OGLRenderRef &OGLRef = *this->ref; + GLsizei w = this->_framebufferWidth; + GLsizei h = this->_framebufferHeight; + + if ( !this->isMultisampledFBOSupported || + (numSamples == 1) || + (w < GPU_FRAMEBUFFER_NATIVE_WIDTH) || (h < GPU_FRAMEBUFFER_NATIVE_HEIGHT) ) + { + return; + } + + if (numSamples == 0) + { + w = 0; + h = 0; + numSamples = 2; + } + + glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, OGLRef.rboMSGColorID); + glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, numSamples, GL_RGBA, w, h); + glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, OGLRef.rboMSGPolyID); + glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, numSamples, GL_RGBA, w, h); + glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, OGLRef.rboMSGFogAttrID); + glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, numSamples, GL_RGBA, w, h); + glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, OGLRef.rboMSGDepthStencilID); + glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, numSamples, GL_DEPTH24_STENCIL8_EXT, w, h); + glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, OGLRef.rboMSGDepthStencilAlphaID); + glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, numSamples, GL_DEPTH24_STENCIL8_EXT, w, h); +} + Render3DError OpenGLRenderer_1_2::InitFinalRenderStates(const std::set *oglExtensionSet) { OGLRenderRef &OGLRef = *this->ref; @@ -4900,23 +4963,10 @@ Render3DError OpenGLRenderer_1_2::SetFramebufferSize(size_t w, size_t h) this->_framebufferPixCount = w * h; this->_framebufferColorSizeBytes = newFramebufferColorSizeBytes; - if (this->isMultisampledFBOSupported) - { - // Call GetLimitedMultisampleSize() after _framebufferWidth and _framebufferHeight are set - // since this method depends on them. - GLsizei sampleSize = this->GetLimitedMultisampleSize(); - - glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, OGLRef.rboMSGColorID); - glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, sampleSize, GL_RGBA, w, h); - glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, OGLRef.rboMSGPolyID); - glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, sampleSize, GL_RGBA, w, h); - glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, OGLRef.rboMSGFogAttrID); - glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, sampleSize, GL_RGBA, w, h); - glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, OGLRef.rboMSGDepthStencilID); - glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, sampleSize, GL_DEPTH24_STENCIL8_EXT, w, h); - glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, OGLRef.rboMSGDepthStencilAlphaID); - glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, sampleSize, GL_DEPTH24_STENCIL8_EXT, w, h); - } + // Call ResizeMultisampledFBOs() after _framebufferWidth and _framebufferHeight are set + // since this method depends on them. + GLsizei sampleSize = this->GetLimitedMultisampleSize(); + this->ResizeMultisampledFBOs(sampleSize); if (this->isPBOSupported) { diff --git a/desmume/src/OGLRender.h b/desmume/src/OGLRender.h index 9d095a581..e64d29ea3 100755 --- a/desmume/src/OGLRender.h +++ b/desmume/src/OGLRender.h @@ -1,7 +1,7 @@ /* Copyright (C) 2006 yopyop Copyright (C) 2006-2007 shash - Copyright (C) 2008-2017 DeSmuME team + Copyright (C) 2008-2018 DeSmuME team This file is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -282,21 +282,6 @@ EXTERNOGLEXT(PFNGLTEXBUFFERPROC, glTexBuffer) // Core in v3.1 #define OGLRENDER_VERT_INDEX_BUFFER_COUNT (POLYLIST_SIZE * 6) -enum OGLMaxMultisamples -{ - OGLMaxMultisamples_Tier1 = 32, - OGLMaxMultisamples_Tier2 = 16, - OGLMaxMultisamples_Tier3 = 8, - OGLMaxMultisamples_Tier4 = 4, -}; - -enum OGLMaxMultisamplesScaleLimit -{ - OGLMaxMultisamplesScaleLimit_Tier1 = 1, - OGLMaxMultisamplesScaleLimit_Tier2 = 4, - OGLMaxMultisamplesScaleLimit_Tier3 = 8 -}; - enum OGLVertexAttributeID { OGLVertexAttributeID_Position = 0, @@ -534,7 +519,7 @@ struct OGLRenderRef // Client-side Buffers GLfloat *color4fBuffer; - GLushort *vertIndexBuffer; + GLushort *vertIndexBuffer; CACHE_ALIGN GLushort workingCIColorBuffer[GPU_FRAMEBUFFER_NATIVE_WIDTH * GPU_FRAMEBUFFER_NATIVE_HEIGHT]; CACHE_ALIGN GLuint workingCIDepthStencilBuffer[2][GPU_FRAMEBUFFER_NATIVE_WIDTH * GPU_FRAMEBUFFER_NATIVE_HEIGHT]; CACHE_ALIGN GLuint workingCIFogAttributesBuffer[2][GPU_FRAMEBUFFER_NATIVE_WIDTH * GPU_FRAMEBUFFER_NATIVE_HEIGHT]; @@ -617,15 +602,15 @@ public: void SetDeposterizeBuffer(void *dstBuffer, void *workingBuffer); void SetUpscalingBuffer(void *upscaleBuffer); }; - -#if defined(ENABLE_AVX2) -class OpenGLRenderer : public Render3D_AVX2 -#elif defined(ENABLE_SSE2) -class OpenGLRenderer : public Render3D_SSE2 -#elif defined(ENABLE_ALTIVEC) -class OpenGLRenderer : public Render3D_Altivec -#else -class OpenGLRenderer : public Render3D + +#if defined(ENABLE_AVX2) +class OpenGLRenderer : public Render3D_AVX2 +#elif defined(ENABLE_SSE2) +class OpenGLRenderer : public Render3D_SSE2 +#elif defined(ENABLE_ALTIVEC) +class OpenGLRenderer : public Render3D_Altivec +#else +class OpenGLRenderer : public Render3D #endif { private: @@ -662,7 +647,8 @@ protected: size_t _currentPolyIndex; OGLTextureUnitID _lastTextureDrawTarget; - bool _enableMultisampledRendering; + bool _enableMultisampledRendering; + int _selectedMultisampleSize; size_t _clearImageIndex; Render3DError FlushFramebuffer(const FragmentColor *__restrict srcFramebuffer, FragmentColor *__restrict dstFramebufferMain, u16 *__restrict dstFramebuffer16); @@ -680,6 +666,7 @@ protected: virtual void DestroyFBOs() = 0; virtual Render3DError CreateMultisampledFBO(GLsizei numSamples) = 0; virtual void DestroyMultisampledFBO() = 0; + virtual void ResizeMultisampledFBOs(GLsizei numSamples) = 0; virtual Render3DError InitGeometryProgram(const char *geometryVtxShaderCString, const char *geometryFragShaderCString, const char *geometryAlphaVtxShaderCString, const char *geometryAlphaFragShaderCString, const char *geometryMSAlphaVtxShaderCString, const char *geometryMSAlphaFragShaderCString) = 0; @@ -751,6 +738,7 @@ protected: virtual void DestroyFBOs(); virtual Render3DError CreateMultisampledFBO(GLsizei numSamples); virtual void DestroyMultisampledFBO(); + virtual void ResizeMultisampledFBOs(GLsizei numSamples); virtual Render3DError CreateVAOs(); virtual void DestroyVAOs(); virtual Render3DError InitFinalRenderStates(const std::set *oglExtensionSet); diff --git a/desmume/src/OGLRender_3_2.cpp b/desmume/src/OGLRender_3_2.cpp index 2ad4fdb98..5c1de9cb0 100644 --- a/desmume/src/OGLRender_3_2.cpp +++ b/desmume/src/OGLRender_3_2.cpp @@ -699,20 +699,37 @@ Render3DError OpenGLRenderer_3_2::InitExtensions() } this->isMultisampledFBOSupported = true; + this->_selectedMultisampleSize = CommonSettings.GFX3D_Renderer_MultisampleSize; GLint maxSamplesOGL = 0; glGetIntegerv(GL_MAX_SAMPLES, &maxSamplesOGL); this->_deviceInfo.maxSamples = (u8)maxSamplesOGL; - if (maxSamplesOGL >= 2) + if (this->_deviceInfo.maxSamples >= 2) { + // Try and initialize the multisampled FBOs with the GFX3D_Renderer_MultisampleSize. + // However, if the client has this set to 0, then set sampleSize to 2 in order to + // force the generation and the attachments of the buffers at a meaningful sample + // size. If GFX3D_Renderer_MultisampleSize is 0, then we can deallocate the buffer + // memory afterwards. GLsizei sampleSize = this->GetLimitedMultisampleSize(); + if (sampleSize == 0) + { + sampleSize = 2; + } error = this->CreateMultisampledFBO(sampleSize); if (error != OGLERROR_NOERR) { this->isMultisampledFBOSupported = false; } + + // If GFX3D_Renderer_MultisampleSize is 0, then we can deallocate the buffers now + // in order to save some memory. + if (this->_selectedMultisampleSize == 0) + { + this->ResizeMultisampledFBOs(0); + } } else { @@ -720,7 +737,7 @@ Render3DError OpenGLRenderer_3_2::InitExtensions() INFO("OpenGL: Driver does not support at least 2x multisampled FBOs.\n"); } - this->_enableMultisampledRendering = (CommonSettings.GFX3D_Renderer_Multisample && this->isMultisampledFBOSupported); + this->_enableMultisampledRendering = ((this->_selectedMultisampleSize >= 2) && this->isMultisampledFBOSupported); this->InitFinalRenderStates(&oglExtensionSet); // This must be done last @@ -1140,6 +1157,47 @@ void OpenGLRenderer_3_2::DestroyMultisampledFBO() this->isMultisampledFBOSupported = false; } +void OpenGLRenderer_3_2::ResizeMultisampledFBOs(GLsizei numSamples) +{ + OGLRenderRef &OGLRef = *this->ref; + GLsizei w = this->_framebufferWidth; + GLsizei h = this->_framebufferHeight; + + if ( !this->isMultisampledFBOSupported || + (numSamples == 1) || + (w < GPU_FRAMEBUFFER_NATIVE_WIDTH) || (h < GPU_FRAMEBUFFER_NATIVE_HEIGHT) ) + { + return; + } + + if (numSamples == 0) + { + w = 0; + h = 0; + numSamples = 2; + } + + if (this->willUsePerSampleZeroDstPass) + { + glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, OGLRef.texMSGColorID); + glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, numSamples, GL_RGBA, w, h, GL_TRUE); + } + else + { + glBindRenderbuffer(GL_RENDERBUFFER, OGLRef.rboMSGColorID); + glRenderbufferStorageMultisample(GL_RENDERBUFFER, numSamples, GL_RGBA, w, h); + } + + glBindRenderbuffer(GL_RENDERBUFFER, OGLRef.rboMSGPolyID); + glRenderbufferStorageMultisample(GL_RENDERBUFFER, numSamples, GL_RGBA, w, h); + glBindRenderbuffer(GL_RENDERBUFFER, OGLRef.rboMSGFogAttrID); + glRenderbufferStorageMultisample(GL_RENDERBUFFER, numSamples, GL_RGBA, w, h); + glBindRenderbuffer(GL_RENDERBUFFER, OGLRef.rboMSGDepthStencilID); + glRenderbufferStorageMultisample(GL_RENDERBUFFER, numSamples, GL_DEPTH24_STENCIL8, w, h); + glBindRenderbuffer(GL_RENDERBUFFER, OGLRef.rboMSGDepthStencilAlphaID); + glRenderbufferStorageMultisample(GL_RENDERBUFFER, numSamples, GL_DEPTH24_STENCIL8, w, h); +} + Render3DError OpenGLRenderer_3_2::CreateVAOs() { OGLRenderRef &OGLRef = *this->ref; @@ -2041,32 +2099,10 @@ Render3DError OpenGLRenderer_3_2::SetFramebufferSize(size_t w, size_t h) this->_framebufferColorSizeBytes = newFramebufferColorSizeBytes; this->_framebufferColor = NULL; // Don't need to make a client-side buffer since we will be reading directly from the PBO. - if (this->isMultisampledFBOSupported) - { - // Call GetLimitedMultisampleSize() after _framebufferWidth and _framebufferHeight are set - // since this method depends on them. - GLsizei sampleSize = this->GetLimitedMultisampleSize(); - - if (this->willUsePerSampleZeroDstPass) - { - glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, OGLRef.texMSGColorID); - glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, sampleSize, GL_RGBA, w, h, GL_TRUE); - } - else - { - glBindRenderbuffer(GL_RENDERBUFFER, OGLRef.rboMSGColorID); - glRenderbufferStorageMultisample(GL_RENDERBUFFER, sampleSize, GL_RGBA, w, h); - } - - glBindRenderbuffer(GL_RENDERBUFFER, OGLRef.rboMSGPolyID); - glRenderbufferStorageMultisample(GL_RENDERBUFFER, sampleSize, GL_RGBA, w, h); - glBindRenderbuffer(GL_RENDERBUFFER, OGLRef.rboMSGFogAttrID); - glRenderbufferStorageMultisample(GL_RENDERBUFFER, sampleSize, GL_RGBA, w, h); - glBindRenderbuffer(GL_RENDERBUFFER, OGLRef.rboMSGDepthStencilID); - glRenderbufferStorageMultisample(GL_RENDERBUFFER, sampleSize, GL_DEPTH24_STENCIL8, w, h); - glBindRenderbuffer(GL_RENDERBUFFER, OGLRef.rboMSGDepthStencilAlphaID); - glRenderbufferStorageMultisample(GL_RENDERBUFFER, sampleSize, GL_DEPTH24_STENCIL8, w, h); - } + // Call ResizeMultisampledFBOs() after _framebufferWidth and _framebufferHeight are set + // since this method depends on them. + GLsizei sampleSize = this->GetLimitedMultisampleSize(); + this->ResizeMultisampledFBOs(sampleSize); glBindTexture(GL_TEXTURE_2D, OGLRef.texGDepthStencilAlphaID); glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, w, h, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, NULL); diff --git a/desmume/src/OGLRender_3_2.h b/desmume/src/OGLRender_3_2.h index d556d93ff..516e2a23e 100644 --- a/desmume/src/OGLRender_3_2.h +++ b/desmume/src/OGLRender_3_2.h @@ -73,6 +73,7 @@ protected: virtual void DestroyFBOs(); virtual Render3DError CreateMultisampledFBO(GLsizei numSamples); virtual void DestroyMultisampledFBO(); + virtual void ResizeMultisampledFBOs(GLsizei numSamples); virtual Render3DError CreateVAOs(); virtual void DestroyVAOs();