1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE examples.
5    Copyright (c) 2020 - Raw Material Software Limited
6 
7    The code included in this file is provided under the terms of the ISC license
8    http://www.isc.org/downloads/software-support-policy/isc-license. Permission
9    To use, copy, modify, and/or distribute this software for any purpose with or
10    without fee is hereby granted provided that the above copyright notice and
11    this permission notice appear in all copies.
12 
13    THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
14    WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
15    PURPOSE, ARE DISCLAIMED.
16 
17   ==============================================================================
18 */
19 
20 /*******************************************************************************
21  The block below describes the properties of this PIP. A PIP is a short snippet
22  of code that can be read by the Projucer and used to generate a JUCE project.
23 
24  BEGIN_JUCE_PIP_METADATA
25 
26  name:             OpenGLDemo2D
27  version:          1.0.0
28  vendor:           JUCE
29  website:          http://juce.com
30  description:      Simple 2D OpenGL application.
31 
32  dependencies:     juce_core, juce_data_structures, juce_events, juce_graphics,
33                    juce_gui_basics, juce_gui_extra, juce_opengl
34  exporters:        xcode_mac, vs2019, linux_make, androidstudio, xcode_iphone
35 
36  moduleFlags:      JUCE_STRICT_REFCOUNTEDPOINTER=1
37 
38  type:             Component
39  mainClass:        OpenGLDemo2D
40 
41  useLocalCopy:     1
42 
43  END_JUCE_PIP_METADATA
44 
45 *******************************************************************************/
46 
47 #pragma once
48 
49 #include "../Assets/DemoUtilities.h"
50 
51 //==============================================================================
52 class OpenGLDemo2D  : public Component,
53                       private CodeDocument::Listener,
54                       private Timer
55 {
56 public:
OpenGLDemo2D()57     OpenGLDemo2D()
58     {
59         setOpaque (true);
60 
61         if (auto* peer = getPeer())
62             peer->setCurrentRenderingEngine (0);
63 
64         openGLContext.attachTo (*getTopLevelComponent());
65 
66         addAndMakeVisible (statusLabel);
67         statusLabel.setJustificationType (Justification::topLeft);
68         statusLabel.setFont (Font (14.0f));
69 
70         auto presets = getPresets();
71 
72         for (int i = 0; i < presets.size(); ++i)
73             presetBox.addItem (presets[i].name, i + 1);
74 
75         addAndMakeVisible (presetLabel);
76         presetLabel.attachToComponent (&presetBox, true);
77 
78         addAndMakeVisible (presetBox);
79         presetBox.onChange = [this] { selectPreset (presetBox.getSelectedItemIndex()); };
80 
81         fragmentEditorComp.setOpaque (false);
82         fragmentDocument.addListener (this);
83         addAndMakeVisible (fragmentEditorComp);
84 
85         presetBox.setSelectedItemIndex (0);
86 
87         setSize (500, 500);
88     }
89 
~OpenGLDemo2D()90     ~OpenGLDemo2D() override
91     {
92         openGLContext.detach();
93         shader.reset();
94     }
95 
paint(Graphics & g)96     void paint (Graphics& g) override
97     {
98         g.fillCheckerBoard (getLocalBounds().toFloat(), 48.0f, 48.0f, Colours::lightgrey, Colours::white);
99 
100         if (shader.get() == nullptr || shader->getFragmentShaderCode() != fragmentCode)
101         {
102             shader.reset();
103 
104             if (fragmentCode.isNotEmpty())
105             {
106                 shader.reset (new OpenGLGraphicsContextCustomShader (fragmentCode));
107 
108                 auto result = shader->checkCompilation (g.getInternalContext());
109 
110                 if (result.failed())
111                 {
112                     statusLabel.setText (result.getErrorMessage(), dontSendNotification);
113                     shader.reset();
114                 }
115             }
116         }
117 
118         if (shader.get() != nullptr)
119         {
120             statusLabel.setText ({}, dontSendNotification);
121 
122             shader->fillRect (g.getInternalContext(), getLocalBounds());
123         }
124     }
125 
resized()126     void resized() override
127     {
128         auto area = getLocalBounds().reduced (4);
129 
130         statusLabel.setBounds (area.removeFromTop (75));
131 
132         area.removeFromTop (area.getHeight() / 2);
133 
134         auto presets = area.removeFromTop (25);
135         presets.removeFromLeft (100);
136         presetBox.setBounds (presets.removeFromLeft (150));
137 
138         area.removeFromTop (4);
139         fragmentEditorComp.setBounds (area);
140     }
141 
selectPreset(int preset)142     void selectPreset (int preset)
143     {
144         fragmentDocument.replaceAllContent (getPresets()[preset].fragmentShader);
145         startTimer (1);
146     }
147 
148     std::unique_ptr<OpenGLGraphicsContextCustomShader> shader;
149 
150     Label statusLabel, presetLabel  { {}, "Shader Preset:" };
151     ComboBox presetBox;
152     CodeDocument fragmentDocument;
153     CodeEditorComponent fragmentEditorComp  { fragmentDocument, nullptr };
154     String fragmentCode;
155 
156 private:
157     OpenGLContext openGLContext;
158 
159     enum { shaderLinkDelay = 500 };
160 
codeDocumentTextInserted(const String &,int)161     void codeDocumentTextInserted (const String& /*newText*/, int /*insertIndex*/) override
162     {
163         startTimer (shaderLinkDelay);
164     }
165 
codeDocumentTextDeleted(int,int)166     void codeDocumentTextDeleted (int /*startIndex*/, int /*endIndex*/) override
167     {
168         startTimer (shaderLinkDelay);
169     }
170 
timerCallback()171     void timerCallback() override
172     {
173         stopTimer();
174         fragmentCode = fragmentDocument.getAllContent();
175         repaint();
176     }
177 
178     struct ShaderPreset
179     {
180         const char* name;
181         const char* fragmentShader;
182     };
183 
getPresets()184     static Array<ShaderPreset> getPresets()
185     {
186         #define SHADER_2DDEMO_HEADER \
187             "/*  This demo shows the use of the OpenGLGraphicsContextCustomShader,\n" \
188             "    which allows a 2D area to be filled using a GL shader program.\n" \
189             "\n" \
190             "    Edit the shader program below and it will be \n" \
191             "    recompiled in real-time!\n" \
192             "*/\n\n"
193 
194         ShaderPreset presets[] =
195         {
196             {
197                 "Simple Gradient",
198 
199                 SHADER_2DDEMO_HEADER
200                 "void main()\n"
201                 "{\n"
202                 "    " JUCE_MEDIUMP " vec4 colour1 = vec4 (1.0, 0.4, 0.6, 1.0);\n"
203                 "    " JUCE_MEDIUMP " vec4 colour2 = vec4 (0.0, 0.8, 0.6, 1.0);\n"
204                 "    " JUCE_MEDIUMP " float alpha = pixelPos.x / 1000.0;\n"
205                 "    gl_FragColor = pixelAlpha * mix (colour1, colour2, alpha);\n"
206                 "}\n"
207             },
208 
209             {
210                 "Circular Gradient",
211 
212                 SHADER_2DDEMO_HEADER
213                 "void main()\n"
214                 "{\n"
215                 "    " JUCE_MEDIUMP " vec4 colour1 = vec4 (1.0, 0.4, 0.6, 1.0);\n"
216                 "    " JUCE_MEDIUMP " vec4 colour2 = vec4 (0.3, 0.4, 0.4, 1.0);\n"
217                 "    " JUCE_MEDIUMP " float alpha = distance (pixelPos, vec2 (600.0, 500.0)) / 400.0;\n"
218                 "    gl_FragColor = pixelAlpha * mix (colour1, colour2, alpha);\n"
219                 "}\n"
220             },
221 
222             {
223                 "Circle",
224 
225                 SHADER_2DDEMO_HEADER
226                 "void main()\n"
227                 "{\n"
228                 "    " JUCE_MEDIUMP " vec4 colour1 = vec4 (0.1, 0.1, 0.9, 1.0);\n"
229                 "    " JUCE_MEDIUMP " vec4 colour2 = vec4 (0.0, 0.8, 0.6, 1.0);\n"
230                 "    " JUCE_MEDIUMP " float distance = distance (pixelPos, vec2 (600.0, 500.0));\n"
231                 "\n"
232                 "    " JUCE_MEDIUMP " float innerRadius = 200.0;\n"
233                 "    " JUCE_MEDIUMP " float outerRadius = 210.0;\n"
234                 "\n"
235                 "    if (distance < innerRadius)\n"
236                 "        gl_FragColor = colour1;\n"
237                 "    else if (distance > outerRadius)\n"
238                 "        gl_FragColor = colour2;\n"
239                 "    else\n"
240                 "        gl_FragColor = mix (colour1, colour2, (distance - innerRadius) / (outerRadius - innerRadius));\n"
241                 "\n"
242                 "    gl_FragColor *= pixelAlpha;\n"
243                 "}\n"
244             },
245 
246             {
247                 "Solid Colour",
248 
249                 SHADER_2DDEMO_HEADER
250                 "void main()\n"
251                 "{\n"
252                 "    gl_FragColor = vec4 (1.0, 0.6, 0.1, pixelAlpha);\n"
253                 "}\n"
254             }
255         };
256 
257         return Array<ShaderPreset> (presets, numElementsInArray (presets));
258     }
259 
260     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OpenGLDemo2D)
261 };
262