OpenGL FSAA tips?

Can any of you resident openGL experts suggest the best way to set up full scene antialiasing within a Juce project? If i try to use glEnable(GL_ARB_MULTISAMPLE) this is undefined so i know there’s more plumbing i have to do somewhere. I’m targeting PC, Mac and iOS, and I know this is a platform specific thing. I’ve been searching old threads, but can’t find a clear explanation of the way one would set this up (at least not one that’s clear to a noob like me).

thanks in advance, n

I have some MSAA enabling code for iOS that i keep meaning to donate to Juce.

I didnt bother with the other platforms because they have enough resolution anyhow, but i had to write this for the ipad mini because its soooo low res that you can otherwise see the pixels really bad!

Hey Hugh, yes please share. Yeah the mini is hard to look at after looking at the retina iPad isn’t it :slight_smile:

For what I’m doing I think I’ve got to get antialiasing working on mac/pc too. If I can do that I can get away with using simple ogl primitives. But right now all i’ve got is polygon smoothing, which is just worthless for what I need to do.

I’m trying to get my head around opengl and it’s quite a bear, especially since all of the books out there use glu/glut shortcuts to explain even basic concepts :roll: – if anyone has suggestions for other places to learn, I’m happy to hear them.

ok, here’s a uDiff. This is the clearest i can make it unfortunately. I can generate HTML differences that look nice but i cant post them :confused:

Jules, the ios.h file is not too bad, you can just take the changes, but the horrible hack i have in OpenGLContext needs attention. basically i just hack in an extra flag for IOS. MSAA/FSAA selection should be made at a more general level, but i wanted to minimise Juce changes here.

the upside, its that MSAA is really worth it on the ipad mini. on the retina pads, it’s better to have a higher frame rate, since you’ve already got 320 dpi.

– hugh

[code]Left base folder: w:\sw\juce
Right base folder: c:\sw\juce
— modules\juce_opengl\opengl\juce_OpenGLContext.h 2013-07-19 11:55:19.000000000 +0100
+++ modules\juce_opengl\opengl\juce_OpenGLContext.h 2013-07-19 12:31:35.000000000 +0100
@@ -125,12 +125,18 @@
/
static OpenGLContext
getCurrentContext();

 /** Asynchronously causes a repaint to be made. */
 void triggerRepaint();
  • // if implemented, call before attach
  • void enableMSAA()
  • {
  •    msaa = true;
    
  • }
  • //==============================================================================
    /** If this context is backed by a frame buffer, this returns its ID number,
    or 0 if the context does not use a framebuffer.
    */
    unsigned int getFrameBufferID() const noexcept;

@@ -240,14 +246,15 @@
NativeContext* nativeContext;
OpenGLRenderer* renderer;
ScopedPointer attachment;
OpenGLPixelFormat pixelFormat;
void* contextToShareWith;
bool renderComponents;

  • bool msaa;

    CachedImage* getCachedImage() const noexcept;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OpenGLContext)
    };

#endif // JUCE_OPENGLCONTEXT_H_INCLUDED
— modules\juce_opengl\opengl\juce_OpenGLContext.cpp 2013-07-19 11:55:19.000000000 +0100
+++ modules\juce_opengl\opengl\juce_OpenGLContext.cpp 2013-07-19 12:31:16.000000000 +0100
@@ -24,25 +24,33 @@

class OpenGLContext::CachedImage : public CachedComponentImage,
public Thread
{
public:
CachedImage (OpenGLContext& c, Component& comp,

  •             const OpenGLPixelFormat& pixFormat, void* contextToShare)
    
  •             const OpenGLPixelFormat& pixFormat,
    
  •             void* contextToShare,
    
  •             bool msaa)
       : Thread ("OpenGL Rendering"),
         context (c), component (comp),
         scale (1.0),
        #if JUCE_OPENGL_ES
         shadersAvailable (true),
        #else
         shadersAvailable (false),
        #endif
         hasInitialised (false),
         needsUpdate (1)
    
    {
  •    // XXX HACK, need common constructor
    

+#ifdef JUCE_IOS

  •    nativeContext = new NativeContext (component, pixFormat, contextToShare, msaa);
    

+#else
nativeContext = new NativeContext (component, pixFormat, contextToShare);
+#endif

     if (nativeContext->createdOk())
         context.nativeContext = nativeContext;
     else
         nativeContext = nullptr;
 }

@@ -493,13 +507,13 @@

 void attach()
 {
     Component& comp = *getComponent();
     CachedImage* const newCachedImage = new CachedImage (context, comp,
                                                          context.pixelFormat,
  •                                                         context.contextToShareWith);
    
  •                                                         context.contextToShareWith, context.msaa);
       comp.setCachedComponentImage (newCachedImage);
       newCachedImage->start(); // (must wait until this is attached before starting its thread)
       newCachedImage->updateViewportSize (true);
    

    }

    void detach()
    @@ -520,12 +534,13 @@

//==============================================================================
OpenGLContext::OpenGLContext()
: nativeContext (nullptr), renderer (nullptr), contextToShareWith (nullptr),
renderComponents (true)
{

  • msaa = false;
    }

OpenGLContext::~OpenGLContext()
{
detach();
}
[/code]

[code]Left base folder: w:\sw\juce
Right base folder: c:\sw\juce
— modules\juce_opengl\native\juce_OpenGL_ios.h 2013-06-27 13:08:11.000000000 +0100
+++ modules\juce_opengl\native\juce_OpenGL_ios.h 2013-07-19 12:30:26.000000000 +0100
@@ -42,17 +42,22 @@

class OpenGLContext::NativeContext
{
public:
NativeContext (Component& component,
const OpenGLPixelFormat& pixelFormat,

  •               void* contextToShareWith)
    
  •               void* contextToShareWith,
    
  •               bool msaa = false)
       : frameBufferHandle (0), colorBufferHandle (0), depthBufferHandle (0),
         lastWidth (0), lastHeight (0), needToRebuildBuffers (false),
    
  •      swapFrames (0), useDepthBuffer (pixelFormat.depthBufferBits > 0)
    
  •      swapFrames (0), useDepthBuffer (pixelFormat.depthBufferBits > 0),
    
  •      useMSAA(msaa)
    
    {
  •    msaaColorHandle = 0;
    
  •    msaaBufferHandle = 0;
    
  •    JUCE_AUTORELEASEPOOL
       {
           ComponentPeer* const peer = component.getPeer();
           jassert (peer != nullptr);
    
           const Rectangle<int> bounds (peer->getComponent().getLocalArea (&component, component.getLocalBounds()));
    

@@ -64,12 +69,13 @@
view.hidden = NO;
view.backgroundColor = [UIColor blackColor];
view.userInteractionEnabled = NO;

         glLayer = (CAEAGLLayer*) [view layer];
         glLayer.contentsScale = Desktop::getInstance().getDisplays().getMainDisplay().scale;
  •    glLayer.opaque = true;
    
           [((UIView*) peer->getNativeHandle()) addSubview: view];
    
           context = [EAGLContext alloc];
    
           const NSUInteger type = kEAGLRenderingAPIOpenGLES2;
    

@@ -111,13 +117,17 @@

 bool makeActive() const noexcept
 {
     if (! [EAGLContext setCurrentContext: context])
         return false;
  •    glBindFramebuffer (GL_FRAMEBUFFER, frameBufferHandle);
    
  •    if (useMSAA)
    
  •        glBindFramebuffer (GL_FRAMEBUFFER, msaaBufferHandle);
    
  •    else
    
  •        glBindFramebuffer (GL_FRAMEBUFFER, frameBufferHandle);
    
  •    return true;
    

    }

    bool isActive() const noexcept
    {
    return [EAGLContext currentContext] == context;
    @@ -127,12 +137,22 @@
    {
    [EAGLContext setCurrentContext: nil];
    }

    void swapBuffers()
    {

  •    if (useMSAA)
    
  •    {
    
  •        glBindFramebuffer(GL_DRAW_FRAMEBUFFER_APPLE, frameBufferHandle);
    
  •        glBindFramebuffer(GL_READ_FRAMEBUFFER_APPLE, msaaBufferHandle);
    
  •        glResolveMultisampleFramebufferAPPLE();
    
  •        const GLenum discards[]  = {GL_COLOR_ATTACHMENT0,GL_DEPTH_ATTACHMENT};
    
  •        glDiscardFramebufferEXT(GL_READ_FRAMEBUFFER_APPLE,2,discards);
    
  •    }
    
  •    glBindRenderbuffer (GL_RENDERBUFFER, colorBufferHandle);
       [context presentRenderbuffer: GL_RENDERBUFFER];
    
       if (needToRebuildBuffers)
       {
           needToRebuildBuffers = false;
    

@@ -167,48 +187,71 @@

private:
JuceGLView* view;
CAEAGLLayer* glLayer;
EAGLContext* context;
GLuint frameBufferHandle, colorBufferHandle, depthBufferHandle;

  • GLuint msaaBufferHandle;

  • GLuint msaaColorHandle;
    int volatile lastWidth, lastHeight;
    bool volatile needToRebuildBuffers;
    int swapFrames;
    bool useDepthBuffer;

  • bool useMSAA;

    //==============================================================================
    void createGLBuffers()
    {
    glGenFramebuffers (1, &frameBufferHandle);
    glGenRenderbuffers (1, &colorBufferHandle);

  •    glBindFramebuffer (GL_FRAMEBUFFER, frameBufferHandle);
       glBindRenderbuffer (GL_RENDERBUFFER, colorBufferHandle);
    
  •    glFramebufferRenderbuffer (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorBufferHandle);
    
  •    bool ok = [context renderbufferStorage: GL_RENDERBUFFER fromDrawable: glLayer];
       jassert (ok); (void) ok;
    
  •    if (useDepthBuffer)
    
  •    {
    
  •        GLint width, height;
    
  •        glGetRenderbufferParameteriv (GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &width);
    
  •        glGetRenderbufferParameteriv (GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &height);
    
  •    GLint width, height;
    
  •    glGetRenderbufferParameteriv (GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &width);
    
  •    glGetRenderbufferParameteriv (GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &height);
    
  •    if (useMSAA)
    
  •    {
    
  •        glGenFramebuffers (1, &msaaBufferHandle);
    
  •        glGenRenderbuffers (1, &msaaColorHandle);
    
  •        glBindFramebuffer (GL_FRAMEBUFFER, msaaBufferHandle);
    
  •        glBindRenderbuffer (GL_RENDERBUFFER, msaaColorHandle);
    
  •        glRenderbufferStorageMultisampleAPPLE(GL_RENDERBUFFER, 4, GL_RGBA8_OES, width, height);
    
  •        glFramebufferRenderbuffer (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, msaaColorHandle);
    
  •    }
    
  •    if (useDepthBuffer)
    
  •    {
           glGenRenderbuffers (1, &depthBufferHandle);
           glBindRenderbuffer (GL_RENDERBUFFER, depthBufferHandle);
    
  •        glRenderbufferStorage (GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height);
    
  •    }
    
  •        if (useMSAA)
    
  •        {
    
  •            glRenderbufferStorageMultisampleAPPLE(GL_RENDERBUFFER, 4, GL_DEPTH_COMPONENT16, width, height);
    
  •        }
    
  •        else
    
  •        {
    
  •            glRenderbufferStorage (GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height);
    
  •        }
    
  •    glBindRenderbuffer (GL_RENDERBUFFER, colorBufferHandle);
    
  •    glBindFramebuffer (GL_FRAMEBUFFER, frameBufferHandle);
    
  •    glFramebufferRenderbuffer (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorBufferHandle);
    
  •    if (useDepthBuffer)
           glFramebufferRenderbuffer (GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthBufferHandle);
    
  •    }
    
       jassert (glCheckFramebufferStatus (GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
       JUCE_CHECK_OPENGL_ERROR
    
  • }

    void freeGLBuffers()
    {
    JUCE_CHECK_OPENGL_ERROR
    [context renderbufferStorage: GL_RENDERBUFFER fromDrawable: nil];
    @@ -228,17 +271,32 @@
    if (depthBufferHandle != 0)
    {
    glDeleteRenderbuffers (1, &depthBufferHandle);
    depthBufferHandle = 0;
    }

  •    if (msaaBufferHandle)
    
  •    {
    
  •        glDeleteFramebuffers(1, &msaaBufferHandle);
    
  •        msaaBufferHandle = 0;
    
  •    }
    
  •    if (msaaColorHandle)
    
  •    {
    
  •        glDeleteRenderbuffers(1, &msaaColorHandle);
    
  •        msaaColorHandle = 0;
    
  •    }
    
  •    JUCE_CHECK_OPENGL_ERROR
    

    }

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NativeContext)
    };

//==============================================================================
bool OpenGLHelpers::isContextActive()
{
return [EAGLContext currentContext] != nil;
}
+
+#undef MSAA
[/code]

Cool - I’ve implemented this now - let me know if it works!

You need the ‘Blue Book’ - the OpenGL SuperBible. In the latest version, they do use a fair amount of provided code, as the new VBO stuff makes getting your first app done so tricky, but I believe it still has great info on creating contexts for each platform.

Side note - how would I tell which iOS platforms need MSAA and which don’t? Is it just enable for everything that doesn’t have retina?

Bruce

Thanks Bruce, I’ll put that on my shopping list!

Does anyone know if MSAA works on windows? My hardware allows for MSAA, I have apps running with it enabled outside of juce, however, when I :

 

context.setMultiSamplingEnabled(true);

it does not look like there is any MSAA.

 

Also, where do you tell juce how many MSAA samples you want? is the default 16x? 8x? 4x? 

 

Thanks!

In the context of JUCE, I believe MSAA is not supported.

But various games on Windows support MSAA, and have for a while (e.g.: Skyrim)...

I got it working on Windows. You need to specify the multisampling level using OpenGLPixelFormat, setting the multisamplingLevel, and calling OpenGLContext::setPixelFormat. Note that all of this has to happen before the OpenGLContext is attached to anything.

1 Like