Gradient rendering on OpenGL


#1

It seems that gradients in OpenGL are switching Red & Blue values, as with the swizzle fix (i.e. removing the swap on Android) all other graphics ops seem to render ok, but not gradients...


#2

I found that by removing the flip of Red and Blue components in juce_OpenGLTexture.cpp (row 115), both Images and Gradients are rendered correctly!

 


#3

Hi Jules, could you check my fix for this ? The fix involves correct colours for gradients and images on Android OpenGL. I suspect that with the swizzle function declaration, the swap of colour components juce_OpenGLTexture.cpp (115) gets obsoleted ?

TIA

/Rob

 


#4

Ok... but I can't imagine that I'd have added the flipping unless it was actually necessary!

On my HTC device, the images all look correct - perhaps the byte-order varies between devices?


#5

Strange... I load a PNG as SoftwareImageType() and when rendering it, blue & red are clearly swapped... same with gradients. This is on an LG Optimus G.


#6

And I can reproduce it in the JuceDemo. Just use this as replacement for OpenGLDemo2D.cpp:


/*
  ==============================================================================
   This file is part of the JUCE library - "Jules' Utility Class Extensions"
   Copyright 2004-12 by Raw Material Software Ltd.
  ------------------------------------------------------------------------------
   JUCE can be redistributed and/or modified under the terms of the GNU General
   Public License (Version 2), as published by the Free Software Foundation.
   A copy of the license is included in the JUCE distribution, or can be found
   online at www.gnu.org/licenses.
   JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
  ------------------------------------------------------------------------------
   To release a closed-source product which uses JUCE, commercial licenses are
   available: visit www.rawmaterialsoftware.com/juce for more information.
  ==============================================================================
*/
#include "../JuceDemoHeader.h"
#if JUCE_OPENGL
//==============================================================================
class OpenGL2DShaderDemo  : public Component,
                            private CodeDocument::Listener,
                            private ComboBox::Listener,
                            private Timer
{
public:
    OpenGL2DShaderDemo()
        : fragmentEditorComp (fragmentDocument, nullptr)
    {
        setOpaque (true);
        MainAppWindow::getMainAppWindow()->setOpenGLRenderingEngine();
        addAndMakeVisible (statusLabel);
        statusLabel.setJustificationType (Justification::topLeft);
        statusLabel.setColour (Label::textColourId, Colours::black);
        statusLabel.setFont (Font (14.0f));
        Array<ShaderPreset> presets (getPresets());
        StringArray presetNames;
        for (int i = 0; i < presets.size(); ++i)
            presetBox.addItem (presets[i].name, i + 1);
        addAndMakeVisible (presetLabel);
        presetLabel.setText ("Shader Preset:", dontSendNotification);
        presetLabel.attachToComponent (&presetBox, true);
        addAndMakeVisible (presetBox);
        presetBox.addListener (this);
        Colour editorBackground (Colours::white.withAlpha (0.6f));
        fragmentEditorComp.setColour (CodeEditorComponent::backgroundColourId, editorBackground);
        fragmentEditorComp.setOpaque (false);
        fragmentDocument.addListener (this);
        addAndMakeVisible (fragmentEditorComp);
        presetBox.setSelectedItemIndex (0);
        testImage = ImageFileFormat::loadFrom(BinaryData::juce_icon_png, BinaryData::juce_icon_pngSize);
    }
    ~OpenGL2DShaderDemo()
    {
        shader = nullptr;
    }
    void paint (Graphics& g)
    {
        g.fillCheckerBoard (getLocalBounds(), 48, 48, Colours::lightgrey, Colours::white);
        if (shader == nullptr || shader->getFragmentShaderCode() != fragmentCode)
        {
            shader = nullptr;
            if (fragmentCode.isNotEmpty())
            {
                shader = new OpenGLGraphicsContextCustomShader (fragmentCode);
                Result result (shader->checkCompilation (g.getInternalContext()));
                if (result.failed())
                {
                    statusLabel.setText (result.getErrorMessage(), dontSendNotification);
                    shader = nullptr;
                }
            }
        }
        if (shader != nullptr)
        {
            statusLabel.setText (String::empty, dontSendNotification);
            shader->fillRect (g.getInternalContext(), getLocalBounds());
        }
        g.drawImageAt(testImage , 0, 0);
    }
    void resized() override
    {
        Rectangle<int> area (getLocalBounds().reduced (4));
        statusLabel.setBounds (area.removeFromTop (75));
        area.removeFromTop (area.getHeight() / 2);
        Rectangle<int> presets (area.removeFromTop (25));
        presets.removeFromLeft (100);
        presetBox.setBounds (presets.removeFromLeft (150));
        area.removeFromTop (4);
        fragmentEditorComp.setBounds (area);
    }
    void selectPreset (int preset)
    {
        fragmentDocument.replaceAllContent (getPresets()[preset].fragmentShader);
        startTimer (1);
    }
    ScopedPointer<OpenGLGraphicsContextCustomShader> shader;
    Label statusLabel, presetLabel;
    ComboBox presetBox;
    CodeDocument fragmentDocument;
    CodeEditorComponent fragmentEditorComp;
    String fragmentCode;
    Image testImage;
private:
    enum { shaderLinkDelay = 500 };
    void codeDocumentTextInserted (const String& /*newText*/, int /*insertIndex*/) override
    {
        startTimer (shaderLinkDelay);
    }
    void codeDocumentTextDeleted (int /*startIndex*/, int /*endIndex*/) override
    {
        startTimer (shaderLinkDelay);
    }
    void timerCallback() override
    {
        stopTimer();
        fragmentCode = fragmentDocument.getAllContent();
        repaint();
    }
    void comboBoxChanged (ComboBox*) override
    {
        selectPreset (presetBox.getSelectedItemIndex());
    }
    struct ShaderPreset
    {
        const char* name;
        const char* fragmentShader;
    };
    static Array<ShaderPreset> getPresets()
    {
        #define SHADER_DEMO_HEADER \
            "/*  This demo shows the use of the OpenGLGraphicsContextCustomShader,\n" \
            "    which allows a 2D area to be filled using a GL shader program.\n" \
            "\n" \
            "    Edit the shader program below and it will be \n" \
            "    recompiled in real-time!\n" \
            "*/\n\n"
        ShaderPreset presets[] =
        {
            {
                "Simple Gradient",
                SHADER_DEMO_HEADER
                "void main()\n"
                "{\n"
                "    " JUCE_MEDIUMP " vec4 colour1 = vec4 (1.0, 0.4, 0.6, 1.0);\n"
                "    " JUCE_MEDIUMP " vec4 colour2 = vec4 (0.0, 0.8, 0.6, 1.0);\n"
                "    " JUCE_MEDIUMP " float alpha = pixelPos.x / 1000.0;\n"
                "    gl_FragColor = pixelAlpha * mix (colour1, colour2, alpha);\n"
                "}\n"
            },
            {
                "Circular Gradient",
                SHADER_DEMO_HEADER
                "void main()\n"
                "{\n"
                "    " JUCE_MEDIUMP " vec4 colour1 = vec4 (1.0, 0.4, 0.6, 1.0);\n"
                "    " JUCE_MEDIUMP " vec4 colour2 = vec4 (0.3, 0.4, 0.4, 1.0);\n"
                "    " JUCE_MEDIUMP " float alpha = distance (pixelPos, vec2 (600.0, 500.0)) / 400.0;\n"
                "    gl_FragColor = pixelAlpha * mix (colour1, colour2, alpha);\n"
                "}\n"
            },
            {
                "Circle",
                SHADER_DEMO_HEADER
                "void main()\n"
                "{\n"
                "    " JUCE_MEDIUMP " vec4 colour1 = vec4 (0.1, 0.1, 0.9, 1.0);\n"
                "    " JUCE_MEDIUMP " vec4 colour2 = vec4 (0.0, 0.8, 0.6, 1.0);\n"
                "    " JUCE_MEDIUMP " float distance = distance (pixelPos, vec2 (600.0, 500.0));\n"
                "\n"
                "    " JUCE_MEDIUMP " float innerRadius = 200.0;\n"
                "    " JUCE_MEDIUMP " float outerRadius = 210.0;\n"
                "\n"
                "    if (distance < innerRadius)\n"
                "        gl_FragColor = colour1;\n"
                "    else if (distance > outerRadius)\n"
                "        gl_FragColor = colour2;\n"
                "    else\n"
                "        gl_FragColor = mix (colour1, colour2, (distance - innerRadius) / (outerRadius - innerRadius));\n"
                "\n"
                "    gl_FragColor *= pixelAlpha;\n"
                "}\n"
            },
            {
                "Solid Colour",
                SHADER_DEMO_HEADER
                "void main()\n"
                "{\n"
                "    gl_FragColor = vec4 (1.0, 0.6, 0.1, pixelAlpha);\n"
                "}\n"
            }
        };
        return Array<ShaderPreset> (presets, numElementsInArray (presets));
    }
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OpenGL2DShaderDemo)
};
//==============================================================================
// This static object will register this demo type in a global list of demos..
static JuceDemoType<OpenGL2DShaderDemo> demo ("20 Graphics: OpenGL 2D");
#endif

and you'll see the swapping of the R&B components.


#7

Sorry to be a nuisance :) but now there is a reproducable case (in JuceDemo), showing the problem (see last message) with JUCE's own png icon.


#8

Understood. Haven't forgotten, but just super-busy right now. Will return to it asap.


#9

Is this the same issue as solved by vipersnake in this thread?

http://www.juce.com/forum/topic/swapped-red-and-blue-colors-colours-bitmaps-displayed-when-opengl-used

 


#10

Yup.


#11

Does my fix on the other thread correct this issue? I have been running it for over a year. 


#12

Hi JUCErs! 

Have you been able to reproduce the OpenGL 2D texture problems ? (see http://www.juce.com/comment/310370#comment-310370)

Edit (again):  No, gradients still are rendered with R&B swapped :(


#13

The gradient problem is still present, as demostrated by the code below (OpenGLDemo2D.cpp):


/*
  ==============================================================================
   This file is part of the JUCE library - "Jules' Utility Class Extensions"
   Copyright 2004-12 by Raw Material Software Ltd.
  ------------------------------------------------------------------------------
   JUCE can be redistributed and/or modified under the terms of the GNU General
   Public License (Version 2), as published by the Free Software Foundation.
   A copy of the license is included in the JUCE distribution, or can be found
   online at www.gnu.org/licenses.
   JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
  ------------------------------------------------------------------------------
   To release a closed-source product which uses JUCE, commercial licenses are
   available: visit www.rawmaterialsoftware.com/juce for more information.
  ==============================================================================
*/
#include "../JuceDemoHeader.h"
#if JUCE_OPENGL
//==============================================================================
class OpenGL2DShaderDemo  : public Component,
                            private CodeDocument::Listener,
                            private ComboBox::Listener,
                            private Timer
{
public:
    OpenGL2DShaderDemo()
        : fragmentEditorComp (fragmentDocument, nullptr)
    {
        setOpaque (true);
        MainAppWindow::getMainAppWindow()->setOpenGLRenderingEngine();
        addAndMakeVisible (statusLabel);
        statusLabel.setJustificationType (Justification::topLeft);
        statusLabel.setColour (Label::textColourId, Colours::black);
        statusLabel.setFont (Font (14.0f));
        Array<ShaderPreset> presets (getPresets());
        StringArray presetNames;
        for (int i = 0; i < presets.size(); ++i)
            presetBox.addItem (presets[i].name, i + 1);
        addAndMakeVisible (presetLabel);
        presetLabel.setText ("Shader Preset:", dontSendNotification);
        presetLabel.attachToComponent (&presetBox, true);
        addAndMakeVisible (presetBox);
        presetBox.addListener (this);
        Colour editorBackground (Colours::white.withAlpha (0.6f));
        fragmentEditorComp.setColour (CodeEditorComponent::backgroundColourId, editorBackground);
        fragmentEditorComp.setOpaque (false);
        fragmentDocument.addListener (this);
        addAndMakeVisible (fragmentEditorComp);
        presetBox.setSelectedItemIndex (0);
        testImage = ImageFileFormat::loadFrom(BinaryData::juce_icon_png, BinaryData::juce_icon_pngSize);
    }
    ~OpenGL2DShaderDemo()
    {
        shader = nullptr;
    }
    void paint (Graphics& g)
    {
        g.fillCheckerBoard (getLocalBounds(), 48, 48, Colours::lightgrey, Colours::white);
        if (shader == nullptr || shader->getFragmentShaderCode() != fragmentCode)
        {
            shader = nullptr;
            if (fragmentCode.isNotEmpty())
            {
                shader = new OpenGLGraphicsContextCustomShader (fragmentCode);
                Result result (shader->checkCompilation (g.getInternalContext()));
                if (result.failed())
                {
                    statusLabel.setText (result.getErrorMessage(), dontSendNotification);
                    shader = nullptr;
                }
            }
        }
        if (shader != nullptr)
        {
            statusLabel.setText (String::empty, dontSendNotification);
            shader->fillRect (g.getInternalContext(), getLocalBounds());
        }
        g.setGradientFill(ColourGradient(Colours::blue, 0.0f, 0.0f, Colours::red, (float)getWidth(), 0.0f, false));
        g.fillRect(0, 0, getWidth(), testImage.getHeight());
        g.drawImageAt(testImage, 0, 0);
    }
    void resized() override
    {
        Rectangle<int> area (getLocalBounds().reduced (4));
        statusLabel.setBounds (area.removeFromTop (75));
        area.removeFromTop (area.getHeight() / 2);
        Rectangle<int> presets (area.removeFromTop (25));
        presets.removeFromLeft (100);
        presetBox.setBounds (presets.removeFromLeft (150));
        area.removeFromTop (4);
        fragmentEditorComp.setBounds (area);
    }
    void selectPreset (int preset)
    {
        fragmentDocument.replaceAllContent (getPresets()[preset].fragmentShader);
        startTimer (1);
    }
    ScopedPointer<OpenGLGraphicsContextCustomShader> shader;
    Label statusLabel, presetLabel;
    ComboBox presetBox;
    CodeDocument fragmentDocument;
    CodeEditorComponent fragmentEditorComp;
    String fragmentCode;
    Image testImage;
private:
    enum { shaderLinkDelay = 500 };
    void codeDocumentTextInserted (const String& /*newText*/, int /*insertIndex*/) override
    {
        startTimer (shaderLinkDelay);
    }
    void codeDocumentTextDeleted (int /*startIndex*/, int /*endIndex*/) override
    {
        startTimer (shaderLinkDelay);
    }
    void timerCallback() override
    {
        stopTimer();
        fragmentCode = fragmentDocument.getAllContent();
        repaint();
    }
    void comboBoxChanged (ComboBox*) override
    {
        selectPreset (presetBox.getSelectedItemIndex());
    }
    struct ShaderPreset
    {
        const char* name;
        const char* fragmentShader;
    };
    static Array<ShaderPreset> getPresets()
    {
        #define SHADER_DEMO_HEADER \
            "/*  This demo shows the use of the OpenGLGraphicsContextCustomShader,\n" \
            "    which allows a 2D area to be filled using a GL shader program.\n" \
            "\n" \
            "    Edit the shader program below and it will be \n" \
            "    recompiled in real-time!\n" \
            "*/\n\n"
        ShaderPreset presets[] =
        {
            {
                "Simple Gradient",
                SHADER_DEMO_HEADER
                "void main()\n"
                "{\n"
                "    " JUCE_MEDIUMP " vec4 colour1 = vec4 (1.0, 0.4, 0.6, 1.0);\n"
                "    " JUCE_MEDIUMP " vec4 colour2 = vec4 (0.0, 0.8, 0.6, 1.0);\n"
                "    " JUCE_MEDIUMP " float alpha = pixelPos.x / 1000.0;\n"
                "    gl_FragColor = pixelAlpha * mix (colour1, colour2, alpha);\n"
                "}\n"
            },
            {
                "Circular Gradient",
                SHADER_DEMO_HEADER
                "void main()\n"
                "{\n"
                "    " JUCE_MEDIUMP " vec4 colour1 = vec4 (1.0, 0.4, 0.6, 1.0);\n"
                "    " JUCE_MEDIUMP " vec4 colour2 = vec4 (0.3, 0.4, 0.4, 1.0);\n"
                "    " JUCE_MEDIUMP " float alpha = distance (pixelPos, vec2 (600.0, 500.0)) / 400.0;\n"
                "    gl_FragColor = pixelAlpha * mix (colour1, colour2, alpha);\n"
                "}\n"
            },
            {
                "Circle",
                SHADER_DEMO_HEADER
                "void main()\n"
                "{\n"
                "    " JUCE_MEDIUMP " vec4 colour1 = vec4 (0.1, 0.1, 0.9, 1.0);\n"
                "    " JUCE_MEDIUMP " vec4 colour2 = vec4 (0.0, 0.8, 0.6, 1.0);\n"
                "    " JUCE_MEDIUMP " float distance = distance (pixelPos, vec2 (600.0, 500.0));\n"
                "\n"
                "    " JUCE_MEDIUMP " float innerRadius = 200.0;\n"
                "    " JUCE_MEDIUMP " float outerRadius = 210.0;\n"
                "\n"
                "    if (distance < innerRadius)\n"
                "        gl_FragColor = colour1;\n"
                "    else if (distance > outerRadius)\n"
                "        gl_FragColor = colour2;\n"
                "    else\n"
                "        gl_FragColor = mix (colour1, colour2, (distance - innerRadius) / (outerRadius - innerRadius));\n"
                "\n"
                "    gl_FragColor *= pixelAlpha;\n"
                "}\n"
            },
            {
                "Solid Colour",
                SHADER_DEMO_HEADER
                "void main()\n"
                "{\n"
                "    gl_FragColor = vec4 (1.0, 0.6, 0.1, pixelAlpha);\n"
                "}\n"
            }
        };
        return Array<ShaderPreset> (presets, numElementsInArray (presets));
    }
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OpenGL2DShaderDemo)
};
//==============================================================================
// This static object will register this demo type in a global list of demos..
static JuceDemoType<OpenGL2DShaderDemo> demo ("20 Graphics: OpenGL 2D");
#endif

The additions is a gradient from blue (left side) to red (right side), and the JUCE logotype. As you'll see, the gradient will be rendered from red to blue instead (due to swapped R&B components).

 


#14

I've removed the swizzle code recently which fixed the red&blue swapping for Images on my Samsung Galaxy Nexus, but it seems to have broken the rendering of solid colours and gradients (I knew the swizzle code must have been there for a reason :-D). I'll look into this on Monday! Thanks for reporting!


#15

Sounds great! Very much appreciated :)

 


#16

Hi Fabian, had any chance to look at this ?

 


#17

Yes, it's fixed. Jules is currently reviewing the code. I'll update this thread once it's public.


#18

It's fixed on the latest tip. Thank you for reporting!


#19

Thanks! Works perfectly.